diff --git a/.gitignore b/.gitignore index 1682ae21..ed98732c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.flatpak-builder *.swp *~ build diff --git a/config.h.meson b/config.h.meson index 34be5f6a..b002f492 100644 --- a/config.h.meson +++ b/config.h.meson @@ -16,6 +16,8 @@ #mesondefine HAVE_EXECINFO_H +#mesondefine HAVE_LIBSYSTEMD + #mesondefine HAVE_PERF_CLOCKID #mesondefine HAVE_POLKIT diff --git a/contrib/eggbitset/COPYING b/contrib/eggbitset/COPYING new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/contrib/eggbitset/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/eggbitset/README.md b/contrib/eggbitset/README.md new file mode 100644 index 00000000..9404a84b --- /dev/null +++ b/contrib/eggbitset/README.md @@ -0,0 +1,16 @@ +Roaring bitmaps implementation +============================== + +This directory contains code modified for GTK, based on the Roaring +bitmaps reference implementation +[CRoaring](https://github.com/RoaringBitmap/CRoaring). + +It is not necessarily compatible with past or future versions of CRoaring, +and replacing it with a different version or linking to a system copy +is not supported. + +See the source files for copyright and licensing information, and the +`COPYING` file for the full text of the Apache license, version 2.0. + +When proposing modifications for these files, please consider whether they +are also suitable for submission to CRoaring. diff --git a/contrib/eggbitset/eggbitset.c b/contrib/eggbitset/eggbitset.c new file mode 100644 index 00000000..f1d45d6f --- /dev/null +++ b/contrib/eggbitset/eggbitset.c @@ -0,0 +1,984 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "eggbitset.h" + +#include "roaring.c" + +/** + * EggBitset: (ref-func egg_bitset_ref) (unref-func egg_bitset_unref) + * + * A `EggBitset` represents a set of unsigned integers. + * + * Another name for this data structure is "bitmap". + * + * The current implementation is based on [roaring bitmaps](https://roaringbitmap.org/). + * + * A bitset allows adding a set of integers and provides support for set operations + * like unions, intersections and checks for equality or if a value is contained + * in the set. `EggBitset` also contains various functions to query metadata about + * the bitset, such as the minimum or maximum values or its size. + * + * The fastest way to iterate values in a bitset is [struct@Egg.BitsetIter]. + * + * The main use case for `EggBitset` is implementing complex selections for + * [iface@Egg.SelectionModel]. + */ + +struct _EggBitset +{ + int ref_count; + roaring_bitmap_t roaring; +}; + + +G_DEFINE_BOXED_TYPE (EggBitset, egg_bitset, + egg_bitset_ref, + egg_bitset_unref) + +/** + * egg_bitset_ref: + * @self: (nullable): a `EggBitset` + * + * Acquires a reference on the given `EggBitset`. + * + * Returns: (transfer none): the `EggBitset` with an additional reference + */ +EggBitset * +egg_bitset_ref (EggBitset *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + self->ref_count += 1; + + return self; +} + +/** + * egg_bitset_unref: + * @self: (nullable): a `EggBitset` + * + * Releases a reference on the given `EggBitset`. + * + * If the reference was the last, the resources associated to the @self are + * freed. + */ +void +egg_bitset_unref (EggBitset *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (self->ref_count > 0); + + self->ref_count -= 1; + if (self->ref_count > 0) + return; + + ra_clear (&self->roaring.high_low_container); + g_free (self); +} + +/** + * egg_bitset_contains: + * @self: a `EggBitset` + * @value: the value to check + * + * Checks if the given @value has been added to @self + * + * Returns: %TRUE if @self contains @value + **/ +gboolean +egg_bitset_contains (const EggBitset *self, + guint value) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return roaring_bitmap_contains (&self->roaring, value); +} + +/** + * egg_bitset_is_empty: + * @self: a `EggBitset` + * + * Check if no value is contained in bitset. + * + * Returns: %TRUE if @self is empty + **/ +gboolean +egg_bitset_is_empty (const EggBitset *self) +{ + g_return_val_if_fail (self != NULL, TRUE); + + return roaring_bitmap_is_empty (&self->roaring); +} + +/** + * egg_bitset_equals: + * @self: a `EggBitset` + * @other: another `EggBitset` + * + * Returns %TRUE if @self and @other contain the same values. + * + * Returns: %TRUE if @self and @other contain the same values + **/ +gboolean +egg_bitset_equals (const EggBitset *self, + const EggBitset *other) +{ + g_return_val_if_fail (self != NULL, other == NULL); + g_return_val_if_fail (other != NULL, FALSE); + + if (self == other) + return TRUE; + + return roaring_bitmap_equals (&self->roaring, &other->roaring); +} + +/** + * egg_bitset_get_minimum: + * @self: a `EggBitset` + * + * Returns the smallest value in @self. + * + * If @self is empty, `G_MAXUINT` is returned. + * + * Returns: The smallest value in @self + **/ +guint +egg_bitset_get_minimum (const EggBitset *self) +{ + g_return_val_if_fail (self != NULL, G_MAXUINT); + + return roaring_bitmap_minimum (&self->roaring); +} + +/** + * egg_bitset_get_maximum: + * @self: a `EggBitset` + * + * Returns the largest value in @self. + * + * If @self is empty, 0 is returned. + * + * Returns: The largest value in @self + **/ +guint +egg_bitset_get_maximum (const EggBitset *self) +{ + g_return_val_if_fail (self != NULL, 0); + + return roaring_bitmap_maximum (&self->roaring); +} + +/** + * egg_bitset_get_size: + * @self: a `EggBitset` + * + * Gets the number of values that were added to the set. + * + * For example, if the set is empty, 0 is returned. + * + * Note that this function returns a `guint64`, because when all + * values are set, the return value is `G_MAXUINT + 1`. Unless you + * are sure this cannot happen (it can't with `GListModel`), be sure + * to use a 64bit type. + * + * Returns: The number of values in the set. + */ +guint64 +egg_bitset_get_size (const EggBitset *self) +{ + g_return_val_if_fail (self != NULL, 0); + + return roaring_bitmap_get_cardinality (&self->roaring); +} + +/** + * egg_bitset_get_size_in_range: + * @self: a `EggBitset` + * @first: the first element to include + * @last: the last element to include + * + * Gets the number of values that are part of the set from @first to @last + * (inclusive). + * + * Note that this function returns a `guint64`, because when all values are + * set, the return value is `G_MAXUINT + 1`. Unless you are sure this cannot + * happen (it can't with `GListModel`), be sure to use a 64bit type. + * + * Returns: The number of values in the set from @first to @last. + */ +guint64 +egg_bitset_get_size_in_range (const EggBitset *self, + guint first, + guint last) +{ + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (last >= first, 0); + + return roaring_bitmap_range_cardinality (&self->roaring, first, ((uint64_t) last) + 1); +} + +/** + * egg_bitset_get_nth: + * @self: a `EggBitset` + * @nth: index of the item to get + * + * Returns the value of the @nth item in self. + * + * If @nth is >= the size of @self, 0 is returned. + * + * Returns: the value of the @nth item in @self + */ +guint +egg_bitset_get_nth (const EggBitset *self, + guint nth) +{ + uint32_t result; + + if (!roaring_bitmap_select (&self->roaring, nth, &result)) + return 0; + + return result; +} + +/** + * egg_bitset_new_empty: + * + * Creates a new empty bitset. + * + * Returns: A new empty bitset + */ +EggBitset * +egg_bitset_new_empty (void) +{ + EggBitset *self; + + self = g_new0 (EggBitset, 1); + + self->ref_count = 1; + + ra_init (&self->roaring.high_low_container); + + return self; +} + +/** + * egg_bitset_new_range: + * @start: first value to add + * @n_items: number of consecutive values to add + * + * Creates a bitset with the given range set. + * + * Returns: A new bitset + **/ +EggBitset * +egg_bitset_new_range (guint start, + guint n_items) +{ + EggBitset *self; + + self = egg_bitset_new_empty (); + + egg_bitset_add_range (self, start, n_items); + + return self; +} + +/** + * egg_bitset_copy: + * @self: a `EggBitset` + * + * Creates a copy of @self. + * + * Returns: (transfer full): A new bitset that contains the same + * values as @self + */ +EggBitset * +egg_bitset_copy (const EggBitset *self) +{ + EggBitset *copy; + + g_return_val_if_fail (self != NULL, NULL); + + copy = egg_bitset_new_empty (); + roaring_bitmap_overwrite (©->roaring, &self->roaring); + + return copy; +} + +/** + * egg_bitset_remove_all: + * @self: a `EggBitset` + * + * Removes all values from the bitset so that it is empty again. + */ +void +egg_bitset_remove_all (EggBitset *self) +{ + g_return_if_fail (self != NULL); + + roaring_bitmap_clear (&self->roaring); +} + +/** + * egg_bitset_add: + * @self: a `EggBitset` + * @value: value to add + * + * Adds @value to @self if it wasn't part of it before. + * + * Returns: %TRUE if @value was not part of @self and @self + * was changed + */ +gboolean +egg_bitset_add (EggBitset *self, + guint value) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return roaring_bitmap_add_checked (&self->roaring, value); +} + +/** + * egg_bitset_remove: + * @self: a `EggBitset` + * @value: value to remove + * + * Removes @value from @self if it was part of it before. + * + * Returns: %TRUE if @value was part of @self and @self + * was changed + */ +gboolean +egg_bitset_remove (EggBitset *self, + guint value) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return roaring_bitmap_remove_checked (&self->roaring, value); +} + +/** + * egg_bitset_add_range: + * @self: a `EggBitset` + * @start: first value to add + * @n_items: number of consecutive values to add + * + * Adds all values from @start (inclusive) to @start + @n_items + * (exclusive) in @self. + */ +void +egg_bitset_add_range (EggBitset *self, + guint start, + guint n_items) +{ + g_return_if_fail (self != NULL); + + if (n_items == 0) + return; + + /* overflow check, the == 0 is to allow add_range(G_MAXUINT, 1); */ + g_return_if_fail (start + n_items == 0 || start + n_items > start); + + roaring_bitmap_add_range_closed (&self->roaring, start, start + n_items - 1); +} + +/** + * egg_bitset_remove_range: + * @self: a `EggBitset` + * @start: first value to remove + * @n_items: number of consecutive values to remove + * + * Removes all values from @start (inclusive) to @start + @n_items (exclusive) + * in @self. + */ +void +egg_bitset_remove_range (EggBitset *self, + guint start, + guint n_items) +{ + g_return_if_fail (self != NULL); + + if (n_items == 0) + return; + + /* overflow check, the == 0 is to allow add_range(G_MAXUINT, 1); */ + g_return_if_fail (start + n_items == 0 || start + n_items > start); + + roaring_bitmap_remove_range_closed (&self->roaring, start, start + n_items - 1); +} + +/** + * egg_bitset_add_range_closed: + * @self: a `EggBitset` + * @first: first value to add + * @last: last value to add + * + * Adds the closed range [@first, @last], so @first, @last and all + * values in between. @first must be smaller than @last. + */ +void +egg_bitset_add_range_closed (EggBitset *self, + guint first, + guint last) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (first <= last); + + roaring_bitmap_add_range_closed (&self->roaring, first, last); +} + +/** + * egg_bitset_remove_range_closed: + * @self: a `EggBitset` + * @first: first value to remove + * @last: last value to remove + * + * Removes the closed range [@first, @last], so @first, @last and all + * values in between. @first must be smaller than @last. + */ +void +egg_bitset_remove_range_closed (EggBitset *self, + guint first, + guint last) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (first <= last); + + roaring_bitmap_remove_range_closed (&self->roaring, first, last); +} + +/** + * egg_bitset_add_rectangle: + * @self: a `EggBitset` + * @start: first value to add + * @width: width of the rectangle + * @height: height of the rectangle + * @stride: row stride of the grid + * + * Interprets the values as a 2-dimensional boolean grid with the given @stride + * and inside that grid, adds a rectangle with the given @width and @height. + */ +void +egg_bitset_add_rectangle (EggBitset *self, + guint start, + guint width, + guint height, + guint stride) +{ + guint i; + + g_return_if_fail (self != NULL); + g_return_if_fail ((start % stride) + width <= stride); + g_return_if_fail (G_MAXUINT - start >= height * stride); + + if (width == 0 || height == 0) + return; + + for (i = 0; i < height; i++) + egg_bitset_add_range (self, i * stride + start, width); +} + +/** + * egg_bitset_remove_rectangle: + * @self: a `EggBitset` + * @start: first value to remove + * @width: width of the rectangle + * @height: height of the rectangle + * @stride: row stride of the grid + * + * Interprets the values as a 2-dimensional boolean grid with the given @stride + * and inside that grid, removes a rectangle with the given @width and @height. + */ +void +egg_bitset_remove_rectangle (EggBitset *self, + guint start, + guint width, + guint height, + guint stride) +{ + guint i; + + g_return_if_fail (self != NULL); + g_return_if_fail (width <= stride); + g_return_if_fail (G_MAXUINT - start >= height * stride); + + if (width == 0 || height == 0) + return; + + for (i = 0; i < height; i++) + egg_bitset_remove_range (self, i * stride + start, width); +} + +/** + * egg_bitset_union: + * @self: a `EggBitset` + * @other: the `EggBitset` to union with + * + * Sets @self to be the union of @self and @other. + * + * That is, add all values from @other into @self that weren't part of it. + * + * It is allowed for @self and @other to be the same bitset. Nothing will + * happen in that case. + */ +void +egg_bitset_union (EggBitset *self, + const EggBitset *other) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (other != NULL); + + if (self == other) + return; + + roaring_bitmap_or_inplace (&self->roaring, &other->roaring); +} + +/** + * egg_bitset_intersect: + * @self: a `EggBitset` + * @other: the `EggBitset` to intersect with + * + * Sets @self to be the intersection of @self and @other. + * + * In other words, remove all values from @self that are not part of @other. + * + * It is allowed for @self and @other to be the same bitset. Nothing will + * happen in that case. + */ +void +egg_bitset_intersect (EggBitset *self, + const EggBitset *other) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (other != NULL); + + if (self == other) + return; + + roaring_bitmap_and_inplace (&self->roaring, &other->roaring); +} + +/** + * egg_bitset_subtract: + * @self: a `EggBitset` + * @other: the `EggBitset` to subtract + * + * Sets @self to be the subtraction of @other from @self. + * + * In other words, remove all values from @self that are part of @other. + * + * It is allowed for @self and @other to be the same bitset. The bitset + * will be emptied in that case. + */ +void +egg_bitset_subtract (EggBitset *self, + const EggBitset *other) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (other != NULL); + + if (self == other) + { + roaring_bitmap_clear (&self->roaring); + return; + } + + roaring_bitmap_andnot_inplace (&self->roaring, &other->roaring); +} + +/** + * egg_bitset_difference: + * @self: a `EggBitset` + * @other: the `EggBitset` to compute the difference from + * + * Sets @self to be the symmetric difference of @self and @other. + * + * The symmetric difference is set @self to contain all values that + * were either contained in @self or in @other, but not in both. + * This operation is also called an XOR. + * + * It is allowed for @self and @other to be the same bitset. The bitset + * will be emptied in that case. + */ +void +egg_bitset_difference (EggBitset *self, + const EggBitset *other) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (other != NULL); + + if (self == other) + { + roaring_bitmap_clear (&self->roaring); + return; + } + + roaring_bitmap_xor_inplace (&self->roaring, &other->roaring); +} + +/** + * egg_bitset_shift_left: + * @self: a `EggBitset` + * @amount: amount to shift all values to the left + * + * Shifts all values in @self to the left by @amount. + * + * Values smaller than @amount are discarded. + */ +void +egg_bitset_shift_left (EggBitset *self, + guint amount) +{ + EggBitset *original; + EggBitsetIter iter; + guint value; + gboolean loop; + + g_return_if_fail (self != NULL); + + if (amount == 0) + return; + + original = egg_bitset_copy (self); + egg_bitset_remove_all (self); + + for (loop = egg_bitset_iter_init_at (&iter, original, amount, &value); + loop; + loop = egg_bitset_iter_next (&iter, &value)) + { + egg_bitset_add (self, value - amount); + } + + egg_bitset_unref (original); +} + +/** + * egg_bitset_shift_right: + * @self: a `EggBitset` + * @amount: amount to shift all values to the right + * + * Shifts all values in @self to the right by @amount. + * + * Values that end up too large to be held in a #guint are discarded. + */ +void +egg_bitset_shift_right (EggBitset *self, + guint amount) +{ + EggBitset *original; + EggBitsetIter iter; + guint value; + gboolean loop; + + g_return_if_fail (self != NULL); + + if (amount == 0) + return; + + original = egg_bitset_copy (self); + egg_bitset_remove_all (self); + + for (loop = egg_bitset_iter_init_first (&iter, original, &value); + loop && value <= G_MAXUINT - amount; + loop = egg_bitset_iter_next (&iter, &value)) + { + egg_bitset_add (self, value + amount); + } + + egg_bitset_unref (original); +} + +/** + * egg_bitset_splice: + * @self: a `EggBitset` + * @position: position at which to slice + * @removed: number of values to remove + * @added: number of values to add + * + * This is a support function for `GListModel` handling, by mirroring + * the `GlistModel::items-changed` signal. + * + * First, it "cuts" the values from @position to @removed from + * the bitset. That is, it removes all those values and shifts + * all larger values to the left by @removed places. + * + * Then, it "pastes" new room into the bitset by shifting all values + * larger than @position by @added spaces to the right. This frees + * up space that can then be filled. + */ +void +egg_bitset_splice (EggBitset *self, + guint position, + guint removed, + guint added) +{ + g_return_if_fail (self != NULL); + /* overflow */ + g_return_if_fail (position + removed >= position); + g_return_if_fail (position + added >= position); + + egg_bitset_remove_range (self, position, removed); + + if (removed != added) + { + EggBitset *shift = egg_bitset_copy (self); + + egg_bitset_remove_range (shift, 0, position); + egg_bitset_remove_range_closed (self, position, G_MAXUINT); + if (added > removed) + egg_bitset_shift_right (shift, added - removed); + else + egg_bitset_shift_left (shift, removed - added); + egg_bitset_union (self, shift); + egg_bitset_unref (shift); + } +} + +G_STATIC_ASSERT (sizeof (EggBitsetIter) >= sizeof (roaring_uint32_iterator_t)); + +static EggBitsetIter * +egg_bitset_iter_copy (EggBitsetIter *iter) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + return (EggBitsetIter *) roaring_copy_uint32_iterator (riter); +} + +static void +egg_bitset_iter_free (EggBitsetIter *iter) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + roaring_free_uint32_iterator (riter); +} + +G_DEFINE_BOXED_TYPE (EggBitsetIter, egg_bitset_iter, egg_bitset_iter_copy, egg_bitset_iter_free) + +/** + * egg_bitset_iter_init_first: + * @iter: (out): a pointer to an uninitialized `EggBitsetIter` + * @set: a `EggBitset` + * @value: (out) (optional): Set to the first value in @set + * + * Initializes an iterator for @set and points it to the first + * value in @set. + * + * If @set is empty, %FALSE is returned and @value is set to %G_MAXUINT. + * + * Returns: %TRUE if @set isn't empty. + */ +gboolean +egg_bitset_iter_init_first (EggBitsetIter *iter, + const EggBitset *set, + guint *value) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (set != NULL, FALSE); + + roaring_init_iterator (&set->roaring, riter); + + if (value) + *value = riter->has_value ? riter->current_value : 0; + + return riter->has_value; +} + +/** + * egg_bitset_iter_init_last: + * @iter: (out): a pointer to an uninitialized `EggBitsetIter` + * @set: a `EggBitset` + * @value: (out) (optional): Set to the last value in @set + * + * Initializes an iterator for @set and points it to the last + * value in @set. + * + * If @set is empty, %FALSE is returned. + * + * Returns: %TRUE if @set isn't empty. + **/ +gboolean +egg_bitset_iter_init_last (EggBitsetIter *iter, + const EggBitset *set, + guint *value) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (set != NULL, FALSE); + + roaring_init_iterator_last (&set->roaring, riter); + + if (value) + *value = riter->has_value ? riter->current_value : 0; + + return riter->has_value; +} + +/** + * egg_bitset_iter_init_at: + * @iter: (out): a pointer to an uninitialized `EggBitsetIter` + * @set: a `EggBitset` + * @target: target value to start iterating at + * @value: (out) (optional): Set to the found value in @set + * + * Initializes @iter to point to @target. + * + * If @target is not found, finds the next value after it. + * If no value >= @target exists in @set, this function returns %FALSE. + * + * Returns: %TRUE if a value was found. + */ +gboolean +egg_bitset_iter_init_at (EggBitsetIter *iter, + const EggBitset *set, + guint target, + guint *value) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (set != NULL, FALSE); + + roaring_init_iterator (&set->roaring, riter); + if (!roaring_move_uint32_iterator_equalorlarger (riter, target)) + { + if (value) + *value = 0; + return FALSE; + } + + if (value) + *value = riter->current_value; + + return TRUE; +} + +/** + * egg_bitset_iter_next: + * @iter: a pointer to a valid `EggBitsetIter` + * @value: (out) (optional): Set to the next value + * + * Moves @iter to the next value in the set. + * + * If it was already pointing to the last value in the set, + * %FALSE is returned and @iter is invalidated. + * + * Returns: %TRUE if a next value existed + */ +gboolean +egg_bitset_iter_next (EggBitsetIter *iter, + guint *value) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + + if (!roaring_advance_uint32_iterator (riter)) + { + if (value) + *value = 0; + return FALSE; + } + + if (value) + *value = riter->current_value; + + return TRUE; +} + +/** + * egg_bitset_iter_previous: + * @iter: a pointer to a valid `EggBitsetIter` + * @value: (out) (optional): Set to the previous value + * + * Moves @iter to the previous value in the set. + * + * If it was already pointing to the first value in the set, + * %FALSE is returned and @iter is invalidated. + * + * Returns: %TRUE if a previous value existed + */ +gboolean +egg_bitset_iter_previous (EggBitsetIter *iter, + guint *value) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + + if (!roaring_previous_uint32_iterator (riter)) + { + if (value) + *value = 0; + return FALSE; + } + + if (value) + *value = riter->current_value; + + return TRUE; +} + +/** + * egg_bitset_iter_get_value: + * @iter: a `EggBitsetIter` + * + * Gets the current value that @iter points to. + * + * If @iter is not valid and [method@Egg.BitsetIter.is_valid] + * returns %FALSE, this function returns 0. + * + * Returns: The current value pointer to by @iter + */ +guint +egg_bitset_iter_get_value (const EggBitsetIter *iter) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, 0); + + if (!riter->has_value) + return 0; + + return riter->current_value; +} + +/** + * egg_bitset_iter_is_valid: + * @iter: a `EggBitsetIter` + * + * Checks if @iter points to a valid value. + * + * Returns: %TRUE if @iter points to a valid value + */ +gboolean +egg_bitset_iter_is_valid (const EggBitsetIter *iter) +{ + roaring_uint32_iterator_t *riter = (roaring_uint32_iterator_t *) iter; + + g_return_val_if_fail (iter != NULL, FALSE); + + return riter->has_value; +} diff --git a/contrib/eggbitset/eggbitset.h b/contrib/eggbitset/eggbitset.h new file mode 100644 index 00000000..3127ce2c --- /dev/null +++ b/contrib/eggbitset/eggbitset.h @@ -0,0 +1,137 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#pragma once + +#include + +G_BEGIN_DECLS + +#define EGG_TYPE_BITSET (egg_bitset_get_type ()) + +typedef struct _EggBitset EggBitset; + +GType egg_bitset_get_type (void) G_GNUC_CONST; +EggBitset * egg_bitset_ref (EggBitset *self); +void egg_bitset_unref (EggBitset *self); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(EggBitset, egg_bitset_unref) + +gboolean egg_bitset_contains (const EggBitset *self, + guint value); +gboolean egg_bitset_is_empty (const EggBitset *self); +gboolean egg_bitset_equals (const EggBitset *self, + const EggBitset *other); +guint64 egg_bitset_get_size (const EggBitset *self); +guint64 egg_bitset_get_size_in_range (const EggBitset *self, + guint first, + guint last); +guint egg_bitset_get_nth (const EggBitset *self, + guint nth); +guint egg_bitset_get_minimum (const EggBitset *self); +guint egg_bitset_get_maximum (const EggBitset *self); + +EggBitset * egg_bitset_new_empty (void); +EggBitset * egg_bitset_copy (const EggBitset *self); +EggBitset * egg_bitset_new_range (guint start, + guint n_items); + +void egg_bitset_remove_all (EggBitset *self); +gboolean egg_bitset_add (EggBitset *self, + guint value); +gboolean egg_bitset_remove (EggBitset *self, + guint value); +void egg_bitset_add_range (EggBitset *self, + guint start, + guint n_items); +void egg_bitset_remove_range (EggBitset *self, + guint start, + guint n_items); +void egg_bitset_add_range_closed (EggBitset *self, + guint first, + guint last); +void egg_bitset_remove_range_closed (EggBitset *self, + guint first, + guint last); +void egg_bitset_add_rectangle (EggBitset *self, + guint start, + guint width, + guint height, + guint stride); +void egg_bitset_remove_rectangle (EggBitset *self, + guint start, + guint width, + guint height, + guint stride); + +void egg_bitset_union (EggBitset *self, + const EggBitset *other); +void egg_bitset_intersect (EggBitset *self, + const EggBitset *other); +void egg_bitset_subtract (EggBitset *self, + const EggBitset *other); +void egg_bitset_difference (EggBitset *self, + const EggBitset *other); +void egg_bitset_shift_left (EggBitset *self, + guint amount); +void egg_bitset_shift_right (EggBitset *self, + guint amount); +void egg_bitset_splice (EggBitset *self, + guint position, + guint removed, + guint added); + +/** + * EggBitsetIter: + * + * An opaque, stack-allocated struct for iterating + * over the elements of a `EggBitset`. + * + * Before a `EggBitsetIter` can be used, it needs to be initialized with + * [func@Egg.BitsetIter.init_first], [func@Egg.BitsetIter.init_last] + * or [func@Egg.BitsetIter.init_at]. + */ +typedef struct _EggBitsetIter EggBitsetIter; + +struct _EggBitsetIter +{ + /*< private >*/ + gpointer private_data[10]; +}; + +GType egg_bitset_iter_get_type (void) G_GNUC_CONST; +gboolean egg_bitset_iter_init_first (EggBitsetIter *iter, + const EggBitset *set, + guint *value); +gboolean egg_bitset_iter_init_last (EggBitsetIter *iter, + const EggBitset *set, + guint *value); +gboolean egg_bitset_iter_init_at (EggBitsetIter *iter, + const EggBitset *set, + guint target, + guint *value); +gboolean egg_bitset_iter_next (EggBitsetIter *iter, + guint *value); +gboolean egg_bitset_iter_previous (EggBitsetIter *iter, + guint *value); +guint egg_bitset_iter_get_value (const EggBitsetIter *iter); +gboolean egg_bitset_iter_is_valid (const EggBitsetIter *iter); + +G_END_DECLS + diff --git a/contrib/eggbitset/meson.build b/contrib/eggbitset/meson.build new file mode 100644 index 00000000..0b5fd036 --- /dev/null +++ b/contrib/eggbitset/meson.build @@ -0,0 +1,17 @@ +libeggbitset_sources = [ + 'eggbitset.c', +] + +libeggbitset_deps = [ + dependency('gio-2.0', version: glib_req_version), +] + +libeggbitset_static = static_library('eggbitset', libeggbitset_sources, + gnu_symbol_visibility: 'hidden', + dependencies: libeggbitset_deps, +) + +libeggbitset_static_dep = declare_dependency( + include_directories: include_directories('.'), + link_with: libeggbitset_static, +) diff --git a/contrib/eggbitset/roaring.c b/contrib/eggbitset/roaring.c new file mode 100644 index 00000000..ff883844 --- /dev/null +++ b/contrib/eggbitset/roaring.c @@ -0,0 +1,11475 @@ +/* + * Amalgamated copy of CRoaring 0.2.66, modified for GTK to reduce compiler + * warnings. + * + * Copyright 2016-2020 The CRoaring authors + * Copyright 2020 Benjamin Otte + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeclaration-after-statement" + +#include "roaring.h" + +/* used for http://dmalloc.com/ Dmalloc - Debug Malloc Library */ +#ifdef DMALLOC +#include "dmalloc.h" +#endif + +/* begin file src/array_util.c */ +#include +#include +#include +#include +#include +#include + + +#ifdef USESSE4 +// used by intersect_vector16 +ALIGNED(0x1000) +static const uint8_t shuffle_mask16[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 6, 7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 6, 7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 6, 7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 6, 7, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 6, 7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 6, 7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 8, 9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 8, 9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 8, 9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 8, 9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 6, 7, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, 8, 9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, + 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 6, 7, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, 8, 9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 6, 7, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 6, 7, 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 10, 11, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 10, 11, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 6, 7, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 6, 7, 10, 11, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 6, 7, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 6, 7, 10, 11, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 6, 7, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 8, 9, 10, 11, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 8, 9, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 8, 9, 10, 11, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 8, 9, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 8, 9, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 6, 7, 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, 8, 9, 10, 11, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 6, 7, 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 6, 7, 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 6, 7, 8, 9, + 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 0xFF, 0xFF, 0xFF, 0xFF, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 6, 7, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 6, 7, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 6, 7, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 6, 7, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 8, 9, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 8, 9, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 8, 9, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 8, 9, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 8, 9, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, + 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 6, 7, 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 6, 7, 8, 9, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, + 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 6, 7, 8, 9, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 6, 7, 8, 9, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 10, 11, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 10, 11, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 6, 7, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, 10, 11, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 6, 7, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 6, 7, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 6, 7, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 6, 7, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 8, 9, + 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 8, 9, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 8, 9, + 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 8, 9, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 6, 7, 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, 8, 9, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, + 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 0xFF, 0xFF, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 6, 7, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 6, 7, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 6, 7, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 6, 7, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 6, 7, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 8, 9, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 8, 9, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 8, 9, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 8, 9, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 8, 9, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 6, 7, 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, 8, 9, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 6, 7, 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 6, 7, 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 6, 7, 8, 9, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 6, 7, 8, 9, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 10, 11, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 10, 11, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 6, 7, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, + 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 6, 7, 10, 11, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 6, 7, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 6, 7, 10, 11, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, + 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 8, 9, 10, 11, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 8, 9, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 8, 9, 10, 11, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 8, 9, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, + 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 6, 7, 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 6, 7, 8, 9, + 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, + 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 0xFF, 0xFF, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 12, 13, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 12, 13, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 6, 7, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 6, 7, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 6, 7, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 6, 7, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 6, 7, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 8, 9, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 8, 9, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 8, 9, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 8, 9, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 6, 7, 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, 8, 9, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, + 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 6, 7, 8, 9, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, 8, 9, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 6, 7, 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 12, 13, 14, 15, 0xFF, 0xFF, 10, 11, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 4, 5, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 10, 11, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 6, 7, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 6, 7, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 6, 7, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 6, 7, 10, 11, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 6, 7, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 4, 5, 6, 7, 10, 11, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, + 8, 9, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 8, 9, 10, 11, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 8, 9, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 4, 5, 8, 9, 10, 11, 12, 13, + 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, + 8, 9, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, + 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, 4, 5, 8, 9, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 1, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 2, 3, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 2, 3, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0xFF, 0xFF, 0xFF, 0xFF, 0, 1, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0xFF, 0xFF, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15}; + +/** + * From Schlegel et al., Fast Sorted-Set Intersection using SIMD Instructions + * Optimized by D. Lemire on May 3rd 2013 + */ +int32_t intersect_vector16(const uint16_t *__restrict__ A, size_t s_a, + const uint16_t *__restrict__ B, size_t s_b, + uint16_t *C) { + size_t count = 0; + size_t i_a = 0, i_b = 0; + const int vectorlength = sizeof(__m128i) / sizeof(uint16_t); + const size_t st_a = (s_a / vectorlength) * vectorlength; + const size_t st_b = (s_b / vectorlength) * vectorlength; + __m128i v_a, v_b; + if ((i_a < st_a) && (i_b < st_b)) { + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + while ((A[i_a] == 0) || (B[i_b] == 0)) { + const __m128i res_v = _mm_cmpestrm( + v_b, vectorlength, v_a, vectorlength, + _SIDD_UWORD_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK); + const int r = _mm_extract_epi32(res_v, 0); + __m128i sm16 = _mm_load_si128((const __m128i *)shuffle_mask16 + r); + __m128i p = _mm_shuffle_epi8(v_a, sm16); + _mm_storeu_si128((__m128i *)&C[count], p); // can overflow + count += _mm_popcnt_u32(r); + const uint16_t a_max = A[i_a + vectorlength - 1]; + const uint16_t b_max = B[i_b + vectorlength - 1]; + if (a_max <= b_max) { + i_a += vectorlength; + if (i_a == st_a) break; + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + } + if (b_max <= a_max) { + i_b += vectorlength; + if (i_b == st_b) break; + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + } + } + if ((i_a < st_a) && (i_b < st_b)) + while (true) { + const __m128i res_v = _mm_cmpistrm( + v_b, v_a, + _SIDD_UWORD_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK); + const int r = _mm_extract_epi32(res_v, 0); + __m128i sm16 = + _mm_load_si128((const __m128i *)shuffle_mask16 + r); + __m128i p = _mm_shuffle_epi8(v_a, sm16); + _mm_storeu_si128((__m128i *)&C[count], p); // can overflow + count += _mm_popcnt_u32(r); + const uint16_t a_max = A[i_a + vectorlength - 1]; + const uint16_t b_max = B[i_b + vectorlength - 1]; + if (a_max <= b_max) { + i_a += vectorlength; + if (i_a == st_a) break; + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + } + if (b_max <= a_max) { + i_b += vectorlength; + if (i_b == st_b) break; + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + } + } + } + // intersect the tail using scalar intersection + while (i_a < s_a && i_b < s_b) { + uint16_t a = A[i_a]; + uint16_t b = B[i_b]; + if (a < b) { + i_a++; + } else if (b < a) { + i_b++; + } else { + C[count] = a; //==b; + count++; + i_a++; + i_b++; + } + } + return (int32_t)count; +} + +int32_t intersect_vector16_cardinality(const uint16_t *__restrict__ A, + size_t s_a, + const uint16_t *__restrict__ B, + size_t s_b) { + size_t count = 0; + size_t i_a = 0, i_b = 0; + const int vectorlength = sizeof(__m128i) / sizeof(uint16_t); + const size_t st_a = (s_a / vectorlength) * vectorlength; + const size_t st_b = (s_b / vectorlength) * vectorlength; + __m128i v_a, v_b; + if ((i_a < st_a) && (i_b < st_b)) { + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + while ((A[i_a] == 0) || (B[i_b] == 0)) { + const __m128i res_v = _mm_cmpestrm( + v_b, vectorlength, v_a, vectorlength, + _SIDD_UWORD_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK); + const int r = _mm_extract_epi32(res_v, 0); + count += _mm_popcnt_u32(r); + const uint16_t a_max = A[i_a + vectorlength - 1]; + const uint16_t b_max = B[i_b + vectorlength - 1]; + if (a_max <= b_max) { + i_a += vectorlength; + if (i_a == st_a) break; + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + } + if (b_max <= a_max) { + i_b += vectorlength; + if (i_b == st_b) break; + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + } + } + if ((i_a < st_a) && (i_b < st_b)) + while (true) { + const __m128i res_v = _mm_cmpistrm( + v_b, v_a, + _SIDD_UWORD_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_BIT_MASK); + const int r = _mm_extract_epi32(res_v, 0); + count += _mm_popcnt_u32(r); + const uint16_t a_max = A[i_a + vectorlength - 1]; + const uint16_t b_max = B[i_b + vectorlength - 1]; + if (a_max <= b_max) { + i_a += vectorlength; + if (i_a == st_a) break; + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + } + if (b_max <= a_max) { + i_b += vectorlength; + if (i_b == st_b) break; + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + } + } + } + // intersect the tail using scalar intersection + while (i_a < s_a && i_b < s_b) { + uint16_t a = A[i_a]; + uint16_t b = B[i_b]; + if (a < b) { + i_a++; + } else if (b < a) { + i_b++; + } else { + count++; + i_a++; + i_b++; + } + } + return (int32_t)count; +} + +///////// +// Warning: +// This function may not be safe if A == C or B == C. +///////// +int32_t difference_vector16(const uint16_t *__restrict__ A, size_t s_a, + const uint16_t *__restrict__ B, size_t s_b, + uint16_t *C) { + // we handle the degenerate case + if (s_a == 0) return 0; + if (s_b == 0) { + if (A != C) memcpy(C, A, sizeof(uint16_t) * s_a); + return (int32_t)s_a; + } + // handle the leading zeroes, it is messy but it allows us to use the fast + // _mm_cmpistrm intrinsic safely + int32_t count = 0; + if ((A[0] == 0) || (B[0] == 0)) { + if ((A[0] == 0) && (B[0] == 0)) { + A++; + s_a--; + B++; + s_b--; + } else if (A[0] == 0) { + C[count++] = 0; + A++; + s_a--; + } else { + B++; + s_b--; + } + } + // at this point, we have two non-empty arrays, made of non-zero + // increasing values. + size_t i_a = 0, i_b = 0; + const size_t vectorlength = sizeof(__m128i) / sizeof(uint16_t); + const size_t st_a = (s_a / vectorlength) * vectorlength; + const size_t st_b = (s_b / vectorlength) * vectorlength; + if ((i_a < st_a) && (i_b < st_b)) { // this is the vectorized code path + __m128i v_a, v_b; //, v_bmax; + // we load a vector from A and a vector from B + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + // we have a runningmask which indicates which values from A have been + // spotted in B, these don't get written out. + __m128i runningmask_a_found_in_b = _mm_setzero_si128(); + /**** + * start of the main vectorized loop + *****/ + while (true) { + // afoundinb will contain a mask indicate for each entry in A + // whether it is seen + // in B + const __m128i a_found_in_b = + _mm_cmpistrm(v_b, v_a, _SIDD_UWORD_OPS | _SIDD_CMP_EQUAL_ANY | + _SIDD_BIT_MASK); + runningmask_a_found_in_b = + _mm_or_si128(runningmask_a_found_in_b, a_found_in_b); + // we always compare the last values of A and B + const uint16_t a_max = A[i_a + vectorlength - 1]; + const uint16_t b_max = B[i_b + vectorlength - 1]; + if (a_max <= b_max) { + // Ok. In this code path, we are ready to write our v_a + // because there is no need to read more from B, they will + // all be large values. + const int bitmask_belongs_to_difference = + _mm_extract_epi32(runningmask_a_found_in_b, 0) ^ 0xFF; + /*** next few lines are probably expensive *****/ + __m128i sm16 = _mm_load_si128((const __m128i *)shuffle_mask16 + + bitmask_belongs_to_difference); + __m128i p = _mm_shuffle_epi8(v_a, sm16); + _mm_storeu_si128((__m128i *)&C[count], p); // can overflow + count += _mm_popcnt_u32(bitmask_belongs_to_difference); + // we advance a + i_a += vectorlength; + if (i_a == st_a) // no more + break; + runningmask_a_found_in_b = _mm_setzero_si128(); + v_a = _mm_lddqu_si128((__m128i *)&A[i_a]); + } + if (b_max <= a_max) { + // in this code path, the current v_b has become useless + i_b += vectorlength; + if (i_b == st_b) break; + v_b = _mm_lddqu_si128((__m128i *)&B[i_b]); + } + } + // at this point, either we have i_a == st_a, which is the end of the + // vectorized processing, + // or we have i_b == st_b, and we are not done processing the vector... + // so we need to finish it off. + if (i_a < st_a) { // we have unfinished business... + uint16_t buffer[8]; // buffer to do a masked load + memset(buffer, 0, 8 * sizeof(uint16_t)); + memcpy(buffer, B + i_b, (s_b - i_b) * sizeof(uint16_t)); + v_b = _mm_lddqu_si128((__m128i *)buffer); + const __m128i a_found_in_b = + _mm_cmpistrm(v_b, v_a, _SIDD_UWORD_OPS | _SIDD_CMP_EQUAL_ANY | + _SIDD_BIT_MASK); + runningmask_a_found_in_b = + _mm_or_si128(runningmask_a_found_in_b, a_found_in_b); + const int bitmask_belongs_to_difference = + _mm_extract_epi32(runningmask_a_found_in_b, 0) ^ 0xFF; + __m128i sm16 = _mm_load_si128((const __m128i *)shuffle_mask16 + + bitmask_belongs_to_difference); + __m128i p = _mm_shuffle_epi8(v_a, sm16); + _mm_storeu_si128((__m128i *)&C[count], p); // can overflow + count += _mm_popcnt_u32(bitmask_belongs_to_difference); + i_a += vectorlength; + } + // at this point we should have i_a == st_a and i_b == st_b + } + // do the tail using scalar code + while (i_a < s_a && i_b < s_b) { + uint16_t a = A[i_a]; + uint16_t b = B[i_b]; + if (b < a) { + i_b++; + } else if (a < b) { + C[count] = a; + count++; + i_a++; + } else { //== + i_a++; + i_b++; + } + } + if (i_a < s_a) { + if(C == A) { + assert((size_t)count <= i_a); + if((size_t)count < i_a) { + memmove(C + count, A + i_a, sizeof(uint16_t) * (s_a - i_a)); + } + } else { + for(size_t i = 0; i < (s_a - i_a); i++) { + C[count + i] = A[i + i_a]; + } + } + count += (int32_t)(s_a - i_a); + } + return count; +} + +#endif // USESSE4 + + + +#ifdef USE_OLD_SKEW_INTERSECT +// TODO: given enough experience with the new skew intersect, drop the old one from the code base. + + +/* Computes the intersection between one small and one large set of uint16_t. + * Stores the result into buffer and return the number of elements. */ +int32_t intersect_skewed_uint16(const uint16_t *small, size_t size_s, + const uint16_t *large, size_t size_l, + uint16_t *buffer) { + size_t pos = 0, idx_l = 0, idx_s = 0; + + if (0 == size_s) { + return 0; + } + + uint16_t val_l = large[idx_l], val_s = small[idx_s]; + + while (true) { + if (val_l < val_s) { + idx_l = advanceUntil(large, (int32_t)idx_l, (int32_t)size_l, val_s); + if (idx_l == size_l) break; + val_l = large[idx_l]; + } else if (val_s < val_l) { + idx_s++; + if (idx_s == size_s) break; + val_s = small[idx_s]; + } else { + buffer[pos++] = val_s; + idx_s++; + if (idx_s == size_s) break; + val_s = small[idx_s]; + idx_l = advanceUntil(large, (int32_t)idx_l, (int32_t)size_l, val_s); + if (idx_l == size_l) break; + val_l = large[idx_l]; + } + } + + return (int32_t)pos; +} +#else // USE_OLD_SKEW_INTERSECT + + +/** +* Branchless binary search going after 4 values at once. +* Assumes that array is sorted. +* You have that array[*index1] >= target1, array[*index12] >= target2, ... +* except when *index1 = n, in which case you know that all values in array are +* smaller than target1, and so forth. +* It has logarithmic complexity. +*/ +static void binarySearch4(const uint16_t *array, int32_t n, uint16_t target1, + uint16_t target2, uint16_t target3, uint16_t target4, + int32_t *index1, int32_t *index2, int32_t *index3, + int32_t *index4) { + const uint16_t *base1 = array; + const uint16_t *base2 = array; + const uint16_t *base3 = array; + const uint16_t *base4 = array; + if (n == 0) + return; + while (n > 1) { + int32_t half = n >> 1; + base1 = (base1[half] < target1) ? &base1[half] : base1; + base2 = (base2[half] < target2) ? &base2[half] : base2; + base3 = (base3[half] < target3) ? &base3[half] : base3; + base4 = (base4[half] < target4) ? &base4[half] : base4; + n -= half; + } + *index1 = (int32_t)((*base1 < target1) + base1 - array); + *index2 = (int32_t)((*base2 < target2) + base2 - array); + *index3 = (int32_t)((*base3 < target3) + base3 - array); + *index4 = (int32_t)((*base4 < target4) + base4 - array); +} + +/** +* Branchless binary search going after 2 values at once. +* Assumes that array is sorted. +* You have that array[*index1] >= target1, array[*index12] >= target2. +* except when *index1 = n, in which case you know that all values in array are +* smaller than target1, and so forth. +* It has logarithmic complexity. +*/ +static void binarySearch2(const uint16_t *array, int32_t n, uint16_t target1, + uint16_t target2, int32_t *index1, int32_t *index2) { + const uint16_t *base1 = array; + const uint16_t *base2 = array; + if (n == 0) + return; + while (n > 1) { + int32_t half = n >> 1; + base1 = (base1[half] < target1) ? &base1[half] : base1; + base2 = (base2[half] < target2) ? &base2[half] : base2; + n -= half; + } + *index1 = (int32_t)((*base1 < target1) + base1 - array); + *index2 = (int32_t)((*base2 < target2) + base2 - array); +} + +/* Computes the intersection between one small and one large set of uint16_t. + * Stores the result into buffer and return the number of elements. + * Processes the small set in blocks of 4 values calling binarySearch4 + * and binarySearch2. This approach can be slightly superior to a conventional + * galloping search in some instances. + */ +int32_t intersect_skewed_uint16(const uint16_t *small, size_t size_s, + const uint16_t *large, size_t size_l, + uint16_t *buffer) { + size_t pos = 0, idx_l = 0, idx_s = 0; + + if (0 == size_s) { + return 0; + } + int32_t index1 = 0, index2 = 0, index3 = 0, index4 = 0; + while ((idx_s + 4 <= size_s) && (idx_l < size_l)) { + uint16_t target1 = small[idx_s]; + uint16_t target2 = small[idx_s + 1]; + uint16_t target3 = small[idx_s + 2]; + uint16_t target4 = small[idx_s + 3]; + binarySearch4(large + idx_l, (int32_t)(size_l - idx_l), target1, target2, target3, + target4, &index1, &index2, &index3, &index4); + if ((index1 + idx_l < size_l) && (large[idx_l + index1] == target1)) { + buffer[pos++] = target1; + } + if ((index2 + idx_l < size_l) && (large[idx_l + index2] == target2)) { + buffer[pos++] = target2; + } + if ((index3 + idx_l < size_l) && (large[idx_l + index3] == target3)) { + buffer[pos++] = target3; + } + if ((index4 + idx_l < size_l) && (large[idx_l + index4] == target4)) { + buffer[pos++] = target4; + } + idx_s += 4; + idx_l += index4; + } + if ((idx_s + 2 <= size_s) && (idx_l < size_l)) { + uint16_t target1 = small[idx_s]; + uint16_t target2 = small[idx_s + 1]; + binarySearch2(large + idx_l, (int32_t)(size_l - idx_l), target1, target2, &index1, + &index2); + if ((index1 + idx_l < size_l) && (large[idx_l + index1] == target1)) { + buffer[pos++] = target1; + } + if ((index2 + idx_l < size_l) && (large[idx_l + index2] == target2)) { + buffer[pos++] = target2; + } + idx_s += 2; + idx_l += index2; + } + if ((idx_s < size_s) && (idx_l < size_l)) { + uint16_t val_s = small[idx_s]; + int32_t index = binarySearch(large + idx_l, (int32_t)(size_l - idx_l), val_s); + if (index >= 0) + buffer[pos++] = val_s; + } + return (int32_t)pos; +} + + +#endif //USE_OLD_SKEW_INTERSECT + + +// TODO: this could be accelerated, possibly, by using binarySearch4 as above. +int32_t intersect_skewed_uint16_cardinality(const uint16_t *small, + size_t size_s, + const uint16_t *large, + size_t size_l) { + size_t pos = 0, idx_l = 0, idx_s = 0; + + if (0 == size_s) { + return 0; + } + + uint16_t val_l = large[idx_l], val_s = small[idx_s]; + + while (true) { + if (val_l < val_s) { + idx_l = advanceUntil(large, (int32_t)idx_l, (int32_t)size_l, val_s); + if (idx_l == size_l) break; + val_l = large[idx_l]; + } else if (val_s < val_l) { + idx_s++; + if (idx_s == size_s) break; + val_s = small[idx_s]; + } else { + pos++; + idx_s++; + if (idx_s == size_s) break; + val_s = small[idx_s]; + idx_l = advanceUntil(large, (int32_t)idx_l, (int32_t)size_l, val_s); + if (idx_l == size_l) break; + val_l = large[idx_l]; + } + } + + return (int32_t)pos; +} + +bool intersect_skewed_uint16_nonempty(const uint16_t *small, size_t size_s, + const uint16_t *large, size_t size_l) { + size_t idx_l = 0, idx_s = 0; + + if (0 == size_s) { + return false; + } + + uint16_t val_l = large[idx_l], val_s = small[idx_s]; + + while (true) { + if (val_l < val_s) { + idx_l = advanceUntil(large, (int32_t)idx_l, (int32_t)size_l, val_s); + if (idx_l == size_l) break; + val_l = large[idx_l]; + } else if (val_s < val_l) { + idx_s++; + if (idx_s == size_s) break; + val_s = small[idx_s]; + } else { + return true; + } + } + + return false; +} + +/** + * Generic intersection function. + */ +int32_t intersect_uint16(const uint16_t *A, const size_t lenA, + const uint16_t *B, const size_t lenB, uint16_t *out) { + const uint16_t *initout = out; + if (lenA == 0 || lenB == 0) return 0; + const uint16_t *endA = A + lenA; + const uint16_t *endB = B + lenB; + + while (1) { + while (*A < *B) { + SKIP_FIRST_COMPARE: + if (++A == endA) return (int32_t)(out - initout); + } + while (*A > *B) { + if (++B == endB) return (int32_t)(out - initout); + } + if (*A == *B) { + *out++ = *A; + if (++A == endA || ++B == endB) return (int32_t)(out - initout); + } else { + goto SKIP_FIRST_COMPARE; + } + } + return (int32_t)(out - initout); // NOTREACHED +} + +int32_t intersect_uint16_cardinality(const uint16_t *A, const size_t lenA, + const uint16_t *B, const size_t lenB) { + int32_t answer = 0; + if (lenA == 0 || lenB == 0) return 0; + const uint16_t *endA = A + lenA; + const uint16_t *endB = B + lenB; + + while (1) { + while (*A < *B) { + SKIP_FIRST_COMPARE: + if (++A == endA) return answer; + } + while (*A > *B) { + if (++B == endB) return answer; + } + if (*A == *B) { + ++answer; + if (++A == endA || ++B == endB) return answer; + } else { + goto SKIP_FIRST_COMPARE; + } + } + return answer; // NOTREACHED +} + + +bool intersect_uint16_nonempty(const uint16_t *A, const size_t lenA, + const uint16_t *B, const size_t lenB) { + if (lenA == 0 || lenB == 0) return 0; + const uint16_t *endA = A + lenA; + const uint16_t *endB = B + lenB; + + while (1) { + while (*A < *B) { + SKIP_FIRST_COMPARE: + if (++A == endA) return false; + } + while (*A > *B) { + if (++B == endB) return false; + } + if (*A == *B) { + return true; + } else { + goto SKIP_FIRST_COMPARE; + } + } + return false; // NOTREACHED +} + + + +/** + * Generic intersection function. + */ +size_t intersection_uint32(const uint32_t *A, const size_t lenA, + const uint32_t *B, const size_t lenB, + uint32_t *out) { + const uint32_t *initout = out; + if (lenA == 0 || lenB == 0) return 0; + const uint32_t *endA = A + lenA; + const uint32_t *endB = B + lenB; + + while (1) { + while (*A < *B) { + SKIP_FIRST_COMPARE: + if (++A == endA) return (out - initout); + } + while (*A > *B) { + if (++B == endB) return (out - initout); + } + if (*A == *B) { + *out++ = *A; + if (++A == endA || ++B == endB) return (out - initout); + } else { + goto SKIP_FIRST_COMPARE; + } + } + return (out - initout); // NOTREACHED +} + +size_t intersection_uint32_card(const uint32_t *A, const size_t lenA, + const uint32_t *B, const size_t lenB) { + if (lenA == 0 || lenB == 0) return 0; + size_t card = 0; + const uint32_t *endA = A + lenA; + const uint32_t *endB = B + lenB; + + while (1) { + while (*A < *B) { + SKIP_FIRST_COMPARE: + if (++A == endA) return card; + } + while (*A > *B) { + if (++B == endB) return card; + } + if (*A == *B) { + card++; + if (++A == endA || ++B == endB) return card; + } else { + goto SKIP_FIRST_COMPARE; + } + } + return card; // NOTREACHED +} + +// can one vectorize the computation of the union? (Update: Yes! See +// union_vector16). + +size_t union_uint16(const uint16_t *set_1, size_t size_1, const uint16_t *set_2, + size_t size_2, uint16_t *buffer) { + size_t pos = 0, idx_1 = 0, idx_2 = 0; + + if (0 == size_2) { + memmove(buffer, set_1, size_1 * sizeof(uint16_t)); + return size_1; + } + if (0 == size_1) { + memmove(buffer, set_2, size_2 * sizeof(uint16_t)); + return size_2; + } + + uint16_t val_1 = set_1[idx_1], val_2 = set_2[idx_2]; + + while (true) { + if (val_1 < val_2) { + buffer[pos++] = val_1; + ++idx_1; + if (idx_1 >= size_1) break; + val_1 = set_1[idx_1]; + } else if (val_2 < val_1) { + buffer[pos++] = val_2; + ++idx_2; + if (idx_2 >= size_2) break; + val_2 = set_2[idx_2]; + } else { + buffer[pos++] = val_1; + ++idx_1; + ++idx_2; + if (idx_1 >= size_1 || idx_2 >= size_2) break; + val_1 = set_1[idx_1]; + val_2 = set_2[idx_2]; + } + } + + if (idx_1 < size_1) { + const size_t n_elems = size_1 - idx_1; + memmove(buffer + pos, set_1 + idx_1, n_elems * sizeof(uint16_t)); + pos += n_elems; + } else if (idx_2 < size_2) { + const size_t n_elems = size_2 - idx_2; + memmove(buffer + pos, set_2 + idx_2, n_elems * sizeof(uint16_t)); + pos += n_elems; + } + + return pos; +} + +int difference_uint16(const uint16_t *a1, int length1, const uint16_t *a2, + int length2, uint16_t *a_out) { + int out_card = 0; + int k1 = 0, k2 = 0; + if (length1 == 0) return 0; + if (length2 == 0) { + if (a1 != a_out) memcpy(a_out, a1, sizeof(uint16_t) * length1); + return length1; + } + uint16_t s1 = a1[k1]; + uint16_t s2 = a2[k2]; + while (true) { + if (s1 < s2) { + a_out[out_card++] = s1; + ++k1; + if (k1 >= length1) { + break; + } + s1 = a1[k1]; + } else if (s1 == s2) { + ++k1; + ++k2; + if (k1 >= length1) { + break; + } + if (k2 >= length2) { + memmove(a_out + out_card, a1 + k1, + sizeof(uint16_t) * (length1 - k1)); + return out_card + length1 - k1; + } + s1 = a1[k1]; + s2 = a2[k2]; + } else { // if (val1>val2) + ++k2; + if (k2 >= length2) { + memmove(a_out + out_card, a1 + k1, + sizeof(uint16_t) * (length1 - k1)); + return out_card + length1 - k1; + } + s2 = a2[k2]; + } + } + return out_card; +} + +int32_t xor_uint16(const uint16_t *array_1, int32_t card_1, + const uint16_t *array_2, int32_t card_2, uint16_t *out) { + int32_t pos1 = 0, pos2 = 0, pos_out = 0; + while (pos1 < card_1 && pos2 < card_2) { + const uint16_t v1 = array_1[pos1]; + const uint16_t v2 = array_2[pos2]; + if (v1 == v2) { + ++pos1; + ++pos2; + continue; + } + if (v1 < v2) { + out[pos_out++] = v1; + ++pos1; + } else { + out[pos_out++] = v2; + ++pos2; + } + } + if (pos1 < card_1) { + const size_t n_elems = card_1 - pos1; + memcpy(out + pos_out, array_1 + pos1, n_elems * sizeof(uint16_t)); + pos_out += (int32_t)n_elems; + } else if (pos2 < card_2) { + const size_t n_elems = card_2 - pos2; + memcpy(out + pos_out, array_2 + pos2, n_elems * sizeof(uint16_t)); + pos_out += (int32_t)n_elems; + } + return pos_out; +} + +#ifdef USESSE4 + +/*** + * start of the SIMD 16-bit union code + * + */ + +// Assuming that vInput1 and vInput2 are sorted, produces a sorted output going +// from vecMin all the way to vecMax +// developed originally for merge sort using SIMD instructions. +// Standard merge. See, e.g., Inoue and Taura, SIMD- and Cache-Friendly +// Algorithm for Sorting an Array of Structures +static inline void sse_merge(const __m128i *vInput1, + const __m128i *vInput2, // input 1 & 2 + __m128i *vecMin, __m128i *vecMax) { // output + __m128i vecTmp; + vecTmp = _mm_min_epu16(*vInput1, *vInput2); + *vecMax = _mm_max_epu16(*vInput1, *vInput2); + vecTmp = _mm_alignr_epi8(vecTmp, vecTmp, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + vecTmp = _mm_alignr_epi8(*vecMin, *vecMin, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + vecTmp = _mm_alignr_epi8(*vecMin, *vecMin, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + vecTmp = _mm_alignr_epi8(*vecMin, *vecMin, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + vecTmp = _mm_alignr_epi8(*vecMin, *vecMin, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + vecTmp = _mm_alignr_epi8(*vecMin, *vecMin, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + vecTmp = _mm_alignr_epi8(*vecMin, *vecMin, 2); + *vecMin = _mm_min_epu16(vecTmp, *vecMax); + *vecMax = _mm_max_epu16(vecTmp, *vecMax); + *vecMin = _mm_alignr_epi8(*vecMin, *vecMin, 2); +} + +// used by store_unique, generated by simdunion.py +static uint8_t uniqshuf[] = { + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xc, 0xd, 0xe, 0xf, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0xa, 0xb, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0xa, 0xb, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0xa, 0xb, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0xa, 0xb, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, + 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x8, 0x9, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8, 0x9, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x6, 0x7, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x6, 0x7, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x6, 0x7, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0xc, 0xd, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0xc, 0xd, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x8, 0x9, 0xa, 0xb, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x8, 0x9, + 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x8, 0x9, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, + 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, 0xa, 0xb, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, + 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x6, 0x7, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0xa, 0xb, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0xa, 0xb, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xa, 0xb, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, + 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x6, 0x7, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x8, 0x9, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x8, 0x9, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x8, 0x9, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8, 0x9, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x6, 0x7, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x6, 0x7, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0xe, 0xf, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0xe, 0xf, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, + 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8, 0x9, 0xa, 0xb, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0xa, 0xb, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, + 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0xa, 0xb, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0xa, 0xb, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0xa, 0xb, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xa, 0xb, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, 0x8, 0x9, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0x8, 0x9, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x8, 0x9, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x8, 0x9, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x8, 0x9, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x6, 0x7, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x6, 0x7, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0xc, 0xd, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0xc, 0xd, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xc, 0xd, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, + 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x8, 0x9, 0xa, 0xb, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x8, 0x9, + 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x8, 0x9, + 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x8, 0x9, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x6, 0x7, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x6, 0x7, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x6, 0x7, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, 0xa, 0xb, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0xa, 0xb, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0xa, 0xb, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0xa, 0xb, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0xa, 0xb, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xa, 0xb, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x6, 0x7, + 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x8, 0x9, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, + 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x4, 0x5, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8, 0x9, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, + 0x6, 0x7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x4, 0x5, 0x6, 0x7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, 0x6, 0x7, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0x6, 0x7, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x6, 0x7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x6, 0x7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x2, 0x3, + 0x4, 0x5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x2, 0x3, 0x4, 0x5, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0x4, 0x5, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4, 0x5, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x0, 0x1, 0x2, 0x3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x2, 0x3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0, 0x1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +// write vector new, while omitting repeated values assuming that previously +// written vector was "old" +static inline int store_unique(__m128i old, __m128i newval, uint16_t *output) { + __m128i vecTmp = _mm_alignr_epi8(newval, old, 16 - 2); + // lots of high latency instructions follow (optimize?) + int M = _mm_movemask_epi8( + _mm_packs_epi16(_mm_cmpeq_epi16(vecTmp, newval), _mm_setzero_si128())); + int numberofnewvalues = 8 - _mm_popcnt_u32(M); + __m128i key = _mm_lddqu_si128((const __m128i *)uniqshuf + M); + __m128i val = _mm_shuffle_epi8(newval, key); + _mm_storeu_si128((__m128i *)output, val); + return numberofnewvalues; +} + +// working in-place, this function overwrites the repeated values +// could be avoided? +static inline uint32_t unique(uint16_t *out, uint32_t len) { + uint32_t pos = 1; + for (uint32_t i = 1; i < len; ++i) { + if (out[i] != out[i - 1]) { + out[pos++] = out[i]; + } + } + return pos; +} + +// use with qsort, could be avoided +static int uint16_compare(const void *a, const void *b) { + return (*(uint16_t *)a - *(uint16_t *)b); +} + +// a one-pass SSE union algorithm +// This function may not be safe if array1 == output or array2 == output. +uint32_t union_vector16(const uint16_t *__restrict__ array1, uint32_t length1, + const uint16_t *__restrict__ array2, uint32_t length2, + uint16_t *__restrict__ output) { + if ((length1 < 8) || (length2 < 8)) { + return (uint32_t)union_uint16(array1, length1, array2, length2, output); + } + __m128i vA, vB, V, vecMin, vecMax; + __m128i laststore; + uint16_t *initoutput = output; + uint32_t len1 = length1 / 8; + uint32_t len2 = length2 / 8; + uint32_t pos1 = 0; + uint32_t pos2 = 0; + // we start the machine + vA = _mm_lddqu_si128((const __m128i *)array1 + pos1); + pos1++; + vB = _mm_lddqu_si128((const __m128i *)array2 + pos2); + pos2++; + sse_merge(&vA, &vB, &vecMin, &vecMax); + laststore = _mm_set1_epi16(-1); + output += store_unique(laststore, vecMin, output); + laststore = vecMin; + if ((pos1 < len1) && (pos2 < len2)) { + uint16_t curA, curB; + curA = array1[8 * pos1]; + curB = array2[8 * pos2]; + while (true) { + if (curA <= curB) { + V = _mm_lddqu_si128((const __m128i *)array1 + pos1); + pos1++; + if (pos1 < len1) { + curA = array1[8 * pos1]; + } else { + break; + } + } else { + V = _mm_lddqu_si128((const __m128i *)array2 + pos2); + pos2++; + if (pos2 < len2) { + curB = array2[8 * pos2]; + } else { + break; + } + } + sse_merge(&V, &vecMax, &vecMin, &vecMax); + output += store_unique(laststore, vecMin, output); + laststore = vecMin; + } + sse_merge(&V, &vecMax, &vecMin, &vecMax); + output += store_unique(laststore, vecMin, output); + laststore = vecMin; + } + // we finish the rest off using a scalar algorithm + // could be improved? + // + // copy the small end on a tmp buffer + uint32_t len = (uint32_t)(output - initoutput); + uint16_t buffer[16]; + uint32_t leftoversize = store_unique(laststore, vecMax, buffer); + if (pos1 == len1) { + memcpy(buffer + leftoversize, array1 + 8 * pos1, + (length1 - 8 * len1) * sizeof(uint16_t)); + leftoversize += length1 - 8 * len1; + qsort(buffer, leftoversize, sizeof(uint16_t), uint16_compare); + + leftoversize = unique(buffer, leftoversize); + len += (uint32_t)union_uint16(buffer, leftoversize, array2 + 8 * pos2, + length2 - 8 * pos2, output); + } else { + memcpy(buffer + leftoversize, array2 + 8 * pos2, + (length2 - 8 * len2) * sizeof(uint16_t)); + leftoversize += length2 - 8 * len2; + qsort(buffer, leftoversize, sizeof(uint16_t), uint16_compare); + leftoversize = unique(buffer, leftoversize); + len += (uint32_t)union_uint16(buffer, leftoversize, array1 + 8 * pos1, + length1 - 8 * pos1, output); + } + return len; +} + +/** + * End of the SIMD 16-bit union code + * + */ + +/** + * Start of SIMD 16-bit XOR code + */ + +// write vector new, while omitting repeated values assuming that previously +// written vector was "old" +static inline int store_unique_xor(__m128i old, __m128i newval, + uint16_t *output) { + __m128i vecTmp1 = _mm_alignr_epi8(newval, old, 16 - 4); + __m128i vecTmp2 = _mm_alignr_epi8(newval, old, 16 - 2); + __m128i equalleft = _mm_cmpeq_epi16(vecTmp2, vecTmp1); + __m128i equalright = _mm_cmpeq_epi16(vecTmp2, newval); + __m128i equalleftoright = _mm_or_si128(equalleft, equalright); + int M = _mm_movemask_epi8( + _mm_packs_epi16(equalleftoright, _mm_setzero_si128())); + int numberofnewvalues = 8 - _mm_popcnt_u32(M); + __m128i key = _mm_lddqu_si128((const __m128i *)uniqshuf + M); + __m128i val = _mm_shuffle_epi8(vecTmp2, key); + _mm_storeu_si128((__m128i *)output, val); + return numberofnewvalues; +} + +// working in-place, this function overwrites the repeated values +// could be avoided? Warning: assumes len > 0 +static inline uint32_t unique_xor(uint16_t *out, uint32_t len) { + uint32_t pos = 1; + for (uint32_t i = 1; i < len; ++i) { + if (out[i] != out[i - 1]) { + out[pos++] = out[i]; + } else + pos--; // if it is identical to previous, delete it + } + return pos; +} + +// a one-pass SSE xor algorithm +uint32_t xor_vector16(const uint16_t *__restrict__ array1, uint32_t length1, + const uint16_t *__restrict__ array2, uint32_t length2, + uint16_t *__restrict__ output) { + if ((length1 < 8) || (length2 < 8)) { + return xor_uint16(array1, length1, array2, length2, output); + } + __m128i vA, vB, V, vecMin, vecMax; + __m128i laststore; + uint16_t *initoutput = output; + uint32_t len1 = length1 / 8; + uint32_t len2 = length2 / 8; + uint32_t pos1 = 0; + uint32_t pos2 = 0; + // we start the machine + vA = _mm_lddqu_si128((const __m128i *)array1 + pos1); + pos1++; + vB = _mm_lddqu_si128((const __m128i *)array2 + pos2); + pos2++; + sse_merge(&vA, &vB, &vecMin, &vecMax); + laststore = _mm_set1_epi16(-1); + uint16_t buffer[17]; + output += store_unique_xor(laststore, vecMin, output); + + laststore = vecMin; + if ((pos1 < len1) && (pos2 < len2)) { + uint16_t curA, curB; + curA = array1[8 * pos1]; + curB = array2[8 * pos2]; + while (true) { + if (curA <= curB) { + V = _mm_lddqu_si128((const __m128i *)array1 + pos1); + pos1++; + if (pos1 < len1) { + curA = array1[8 * pos1]; + } else { + break; + } + } else { + V = _mm_lddqu_si128((const __m128i *)array2 + pos2); + pos2++; + if (pos2 < len2) { + curB = array2[8 * pos2]; + } else { + break; + } + } + sse_merge(&V, &vecMax, &vecMin, &vecMax); + // conditionally stores the last value of laststore as well as all + // but the + // last value of vecMin + output += store_unique_xor(laststore, vecMin, output); + laststore = vecMin; + } + sse_merge(&V, &vecMax, &vecMin, &vecMax); + // conditionally stores the last value of laststore as well as all but + // the + // last value of vecMin + output += store_unique_xor(laststore, vecMin, output); + laststore = vecMin; + } + uint32_t len = (uint32_t)(output - initoutput); + + // we finish the rest off using a scalar algorithm + // could be improved? + // conditionally stores the last value of laststore as well as all but the + // last value of vecMax, + // we store to "buffer" + int leftoversize = store_unique_xor(laststore, vecMax, buffer); + uint16_t vec7 = _mm_extract_epi16(vecMax, 7); + uint16_t vec6 = _mm_extract_epi16(vecMax, 6); + if (vec7 != vec6) buffer[leftoversize++] = vec7; + if (pos1 == len1) { + memcpy(buffer + leftoversize, array1 + 8 * pos1, + (length1 - 8 * len1) * sizeof(uint16_t)); + leftoversize += length1 - 8 * len1; + if (leftoversize == 0) { // trivial case + memcpy(output, array2 + 8 * pos2, + (length2 - 8 * pos2) * sizeof(uint16_t)); + len += (length2 - 8 * pos2); + } else { + qsort(buffer, leftoversize, sizeof(uint16_t), uint16_compare); + leftoversize = unique_xor(buffer, leftoversize); + len += xor_uint16(buffer, leftoversize, array2 + 8 * pos2, + length2 - 8 * pos2, output); + } + } else { + memcpy(buffer + leftoversize, array2 + 8 * pos2, + (length2 - 8 * len2) * sizeof(uint16_t)); + leftoversize += length2 - 8 * len2; + if (leftoversize == 0) { // trivial case + memcpy(output, array1 + 8 * pos1, + (length1 - 8 * pos1) * sizeof(uint16_t)); + len += (length1 - 8 * pos1); + } else { + qsort(buffer, leftoversize, sizeof(uint16_t), uint16_compare); + leftoversize = unique_xor(buffer, leftoversize); + len += xor_uint16(buffer, leftoversize, array1 + 8 * pos1, + length1 - 8 * pos1, output); + } + } + return len; +} + +/** + * End of SIMD 16-bit XOR code + */ + +#endif // USESSE4 + +size_t union_uint32(const uint32_t *set_1, size_t size_1, const uint32_t *set_2, + size_t size_2, uint32_t *buffer) { + size_t pos = 0, idx_1 = 0, idx_2 = 0; + + if (0 == size_2) { + memmove(buffer, set_1, size_1 * sizeof(uint32_t)); + return size_1; + } + if (0 == size_1) { + memmove(buffer, set_2, size_2 * sizeof(uint32_t)); + return size_2; + } + + uint32_t val_1 = set_1[idx_1], val_2 = set_2[idx_2]; + + while (true) { + if (val_1 < val_2) { + buffer[pos++] = val_1; + ++idx_1; + if (idx_1 >= size_1) break; + val_1 = set_1[idx_1]; + } else if (val_2 < val_1) { + buffer[pos++] = val_2; + ++idx_2; + if (idx_2 >= size_2) break; + val_2 = set_2[idx_2]; + } else { + buffer[pos++] = val_1; + ++idx_1; + ++idx_2; + if (idx_1 >= size_1 || idx_2 >= size_2) break; + val_1 = set_1[idx_1]; + val_2 = set_2[idx_2]; + } + } + + if (idx_1 < size_1) { + const size_t n_elems = size_1 - idx_1; + memmove(buffer + pos, set_1 + idx_1, n_elems * sizeof(uint32_t)); + pos += n_elems; + } else if (idx_2 < size_2) { + const size_t n_elems = size_2 - idx_2; + memmove(buffer + pos, set_2 + idx_2, n_elems * sizeof(uint32_t)); + pos += n_elems; + } + + return pos; +} + +size_t union_uint32_card(const uint32_t *set_1, size_t size_1, + const uint32_t *set_2, size_t size_2) { + size_t pos = 0, idx_1 = 0, idx_2 = 0; + + if (0 == size_2) { + return size_1; + } + if (0 == size_1) { + return size_2; + } + + uint32_t val_1 = set_1[idx_1], val_2 = set_2[idx_2]; + + while (true) { + if (val_1 < val_2) { + ++idx_1; + ++pos; + if (idx_1 >= size_1) break; + val_1 = set_1[idx_1]; + } else if (val_2 < val_1) { + ++idx_2; + ++pos; + if (idx_2 >= size_2) break; + val_2 = set_2[idx_2]; + } else { + ++idx_1; + ++idx_2; + ++pos; + if (idx_1 >= size_1 || idx_2 >= size_2) break; + val_1 = set_1[idx_1]; + val_2 = set_2[idx_2]; + } + } + + if (idx_1 < size_1) { + const size_t n_elems = size_1 - idx_1; + pos += n_elems; + } else if (idx_2 < size_2) { + const size_t n_elems = size_2 - idx_2; + pos += n_elems; + } + return pos; +} + + + +size_t fast_union_uint16(const uint16_t *set_1, size_t size_1, const uint16_t *set_2, + size_t size_2, uint16_t *buffer) { +#ifdef ROARING_VECTOR_OPERATIONS_ENABLED + // compute union with smallest array first + if (size_1 < size_2) { + return union_vector16(set_1, (uint32_t)size_1, + set_2, (uint32_t)size_2, buffer); + } else { + return union_vector16(set_2, (uint32_t)size_2, + set_1, (uint32_t)size_1, buffer); + } +#else + // compute union with smallest array first + if (size_1 < size_2) { + return union_uint16( + set_1, size_1, set_2, size_2, buffer); + } else { + return union_uint16( + set_2, size_2, set_1, size_1, buffer); + } +#endif +} + +bool memequals(const void *s1, const void *s2, size_t n) { + if (n == 0) { + return true; + } +#ifdef USEAVX + const uint8_t *ptr1 = (const uint8_t *)s1; + const uint8_t *ptr2 = (const uint8_t *)s2; + const uint8_t *end1 = ptr1 + n; + const uint8_t *end8 = ptr1 + n/8*8; + const uint8_t *end32 = ptr1 + n/32*32; + + while (ptr1 < end32) { + __m256i r1 = _mm256_loadu_si256((const __m256i*)ptr1); + __m256i r2 = _mm256_loadu_si256((const __m256i*)ptr2); + int mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(r1, r2)); + if ((uint32_t)mask != UINT32_MAX) { + return false; + } + ptr1 += 32; + ptr2 += 32; + } + + while (ptr1 < end8) { + uint64_t v1 = *((const uint64_t*)ptr1); + uint64_t v2 = *((const uint64_t*)ptr2); + if (v1 != v2) { + return false; + } + ptr1 += 8; + ptr2 += 8; + } + + while (ptr1 < end1) { + if (*ptr1 != *ptr2) { + return false; + } + ptr1++; + ptr2++; + } + + return true; +#else + return memcmp(s1, s2, n) == 0; +#endif +} +/* end file src/array_util.c */ +/* begin file src/bitset_util.c */ +#include +#include +#include +#include +#include + + +#ifdef IS_X64 +static uint8_t lengthTable[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, + 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, + 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, + 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; +#endif + +#ifdef USEAVX +ALIGNED(32) +static uint32_t vecDecodeTable[256][8] = { + {0, 0, 0, 0, 0, 0, 0, 0}, /* 0x00 (00000000) */ + {1, 0, 0, 0, 0, 0, 0, 0}, /* 0x01 (00000001) */ + {2, 0, 0, 0, 0, 0, 0, 0}, /* 0x02 (00000010) */ + {1, 2, 0, 0, 0, 0, 0, 0}, /* 0x03 (00000011) */ + {3, 0, 0, 0, 0, 0, 0, 0}, /* 0x04 (00000100) */ + {1, 3, 0, 0, 0, 0, 0, 0}, /* 0x05 (00000101) */ + {2, 3, 0, 0, 0, 0, 0, 0}, /* 0x06 (00000110) */ + {1, 2, 3, 0, 0, 0, 0, 0}, /* 0x07 (00000111) */ + {4, 0, 0, 0, 0, 0, 0, 0}, /* 0x08 (00001000) */ + {1, 4, 0, 0, 0, 0, 0, 0}, /* 0x09 (00001001) */ + {2, 4, 0, 0, 0, 0, 0, 0}, /* 0x0A (00001010) */ + {1, 2, 4, 0, 0, 0, 0, 0}, /* 0x0B (00001011) */ + {3, 4, 0, 0, 0, 0, 0, 0}, /* 0x0C (00001100) */ + {1, 3, 4, 0, 0, 0, 0, 0}, /* 0x0D (00001101) */ + {2, 3, 4, 0, 0, 0, 0, 0}, /* 0x0E (00001110) */ + {1, 2, 3, 4, 0, 0, 0, 0}, /* 0x0F (00001111) */ + {5, 0, 0, 0, 0, 0, 0, 0}, /* 0x10 (00010000) */ + {1, 5, 0, 0, 0, 0, 0, 0}, /* 0x11 (00010001) */ + {2, 5, 0, 0, 0, 0, 0, 0}, /* 0x12 (00010010) */ + {1, 2, 5, 0, 0, 0, 0, 0}, /* 0x13 (00010011) */ + {3, 5, 0, 0, 0, 0, 0, 0}, /* 0x14 (00010100) */ + {1, 3, 5, 0, 0, 0, 0, 0}, /* 0x15 (00010101) */ + {2, 3, 5, 0, 0, 0, 0, 0}, /* 0x16 (00010110) */ + {1, 2, 3, 5, 0, 0, 0, 0}, /* 0x17 (00010111) */ + {4, 5, 0, 0, 0, 0, 0, 0}, /* 0x18 (00011000) */ + {1, 4, 5, 0, 0, 0, 0, 0}, /* 0x19 (00011001) */ + {2, 4, 5, 0, 0, 0, 0, 0}, /* 0x1A (00011010) */ + {1, 2, 4, 5, 0, 0, 0, 0}, /* 0x1B (00011011) */ + {3, 4, 5, 0, 0, 0, 0, 0}, /* 0x1C (00011100) */ + {1, 3, 4, 5, 0, 0, 0, 0}, /* 0x1D (00011101) */ + {2, 3, 4, 5, 0, 0, 0, 0}, /* 0x1E (00011110) */ + {1, 2, 3, 4, 5, 0, 0, 0}, /* 0x1F (00011111) */ + {6, 0, 0, 0, 0, 0, 0, 0}, /* 0x20 (00100000) */ + {1, 6, 0, 0, 0, 0, 0, 0}, /* 0x21 (00100001) */ + {2, 6, 0, 0, 0, 0, 0, 0}, /* 0x22 (00100010) */ + {1, 2, 6, 0, 0, 0, 0, 0}, /* 0x23 (00100011) */ + {3, 6, 0, 0, 0, 0, 0, 0}, /* 0x24 (00100100) */ + {1, 3, 6, 0, 0, 0, 0, 0}, /* 0x25 (00100101) */ + {2, 3, 6, 0, 0, 0, 0, 0}, /* 0x26 (00100110) */ + {1, 2, 3, 6, 0, 0, 0, 0}, /* 0x27 (00100111) */ + {4, 6, 0, 0, 0, 0, 0, 0}, /* 0x28 (00101000) */ + {1, 4, 6, 0, 0, 0, 0, 0}, /* 0x29 (00101001) */ + {2, 4, 6, 0, 0, 0, 0, 0}, /* 0x2A (00101010) */ + {1, 2, 4, 6, 0, 0, 0, 0}, /* 0x2B (00101011) */ + {3, 4, 6, 0, 0, 0, 0, 0}, /* 0x2C (00101100) */ + {1, 3, 4, 6, 0, 0, 0, 0}, /* 0x2D (00101101) */ + {2, 3, 4, 6, 0, 0, 0, 0}, /* 0x2E (00101110) */ + {1, 2, 3, 4, 6, 0, 0, 0}, /* 0x2F (00101111) */ + {5, 6, 0, 0, 0, 0, 0, 0}, /* 0x30 (00110000) */ + {1, 5, 6, 0, 0, 0, 0, 0}, /* 0x31 (00110001) */ + {2, 5, 6, 0, 0, 0, 0, 0}, /* 0x32 (00110010) */ + {1, 2, 5, 6, 0, 0, 0, 0}, /* 0x33 (00110011) */ + {3, 5, 6, 0, 0, 0, 0, 0}, /* 0x34 (00110100) */ + {1, 3, 5, 6, 0, 0, 0, 0}, /* 0x35 (00110101) */ + {2, 3, 5, 6, 0, 0, 0, 0}, /* 0x36 (00110110) */ + {1, 2, 3, 5, 6, 0, 0, 0}, /* 0x37 (00110111) */ + {4, 5, 6, 0, 0, 0, 0, 0}, /* 0x38 (00111000) */ + {1, 4, 5, 6, 0, 0, 0, 0}, /* 0x39 (00111001) */ + {2, 4, 5, 6, 0, 0, 0, 0}, /* 0x3A (00111010) */ + {1, 2, 4, 5, 6, 0, 0, 0}, /* 0x3B (00111011) */ + {3, 4, 5, 6, 0, 0, 0, 0}, /* 0x3C (00111100) */ + {1, 3, 4, 5, 6, 0, 0, 0}, /* 0x3D (00111101) */ + {2, 3, 4, 5, 6, 0, 0, 0}, /* 0x3E (00111110) */ + {1, 2, 3, 4, 5, 6, 0, 0}, /* 0x3F (00111111) */ + {7, 0, 0, 0, 0, 0, 0, 0}, /* 0x40 (01000000) */ + {1, 7, 0, 0, 0, 0, 0, 0}, /* 0x41 (01000001) */ + {2, 7, 0, 0, 0, 0, 0, 0}, /* 0x42 (01000010) */ + {1, 2, 7, 0, 0, 0, 0, 0}, /* 0x43 (01000011) */ + {3, 7, 0, 0, 0, 0, 0, 0}, /* 0x44 (01000100) */ + {1, 3, 7, 0, 0, 0, 0, 0}, /* 0x45 (01000101) */ + {2, 3, 7, 0, 0, 0, 0, 0}, /* 0x46 (01000110) */ + {1, 2, 3, 7, 0, 0, 0, 0}, /* 0x47 (01000111) */ + {4, 7, 0, 0, 0, 0, 0, 0}, /* 0x48 (01001000) */ + {1, 4, 7, 0, 0, 0, 0, 0}, /* 0x49 (01001001) */ + {2, 4, 7, 0, 0, 0, 0, 0}, /* 0x4A (01001010) */ + {1, 2, 4, 7, 0, 0, 0, 0}, /* 0x4B (01001011) */ + {3, 4, 7, 0, 0, 0, 0, 0}, /* 0x4C (01001100) */ + {1, 3, 4, 7, 0, 0, 0, 0}, /* 0x4D (01001101) */ + {2, 3, 4, 7, 0, 0, 0, 0}, /* 0x4E (01001110) */ + {1, 2, 3, 4, 7, 0, 0, 0}, /* 0x4F (01001111) */ + {5, 7, 0, 0, 0, 0, 0, 0}, /* 0x50 (01010000) */ + {1, 5, 7, 0, 0, 0, 0, 0}, /* 0x51 (01010001) */ + {2, 5, 7, 0, 0, 0, 0, 0}, /* 0x52 (01010010) */ + {1, 2, 5, 7, 0, 0, 0, 0}, /* 0x53 (01010011) */ + {3, 5, 7, 0, 0, 0, 0, 0}, /* 0x54 (01010100) */ + {1, 3, 5, 7, 0, 0, 0, 0}, /* 0x55 (01010101) */ + {2, 3, 5, 7, 0, 0, 0, 0}, /* 0x56 (01010110) */ + {1, 2, 3, 5, 7, 0, 0, 0}, /* 0x57 (01010111) */ + {4, 5, 7, 0, 0, 0, 0, 0}, /* 0x58 (01011000) */ + {1, 4, 5, 7, 0, 0, 0, 0}, /* 0x59 (01011001) */ + {2, 4, 5, 7, 0, 0, 0, 0}, /* 0x5A (01011010) */ + {1, 2, 4, 5, 7, 0, 0, 0}, /* 0x5B (01011011) */ + {3, 4, 5, 7, 0, 0, 0, 0}, /* 0x5C (01011100) */ + {1, 3, 4, 5, 7, 0, 0, 0}, /* 0x5D (01011101) */ + {2, 3, 4, 5, 7, 0, 0, 0}, /* 0x5E (01011110) */ + {1, 2, 3, 4, 5, 7, 0, 0}, /* 0x5F (01011111) */ + {6, 7, 0, 0, 0, 0, 0, 0}, /* 0x60 (01100000) */ + {1, 6, 7, 0, 0, 0, 0, 0}, /* 0x61 (01100001) */ + {2, 6, 7, 0, 0, 0, 0, 0}, /* 0x62 (01100010) */ + {1, 2, 6, 7, 0, 0, 0, 0}, /* 0x63 (01100011) */ + {3, 6, 7, 0, 0, 0, 0, 0}, /* 0x64 (01100100) */ + {1, 3, 6, 7, 0, 0, 0, 0}, /* 0x65 (01100101) */ + {2, 3, 6, 7, 0, 0, 0, 0}, /* 0x66 (01100110) */ + {1, 2, 3, 6, 7, 0, 0, 0}, /* 0x67 (01100111) */ + {4, 6, 7, 0, 0, 0, 0, 0}, /* 0x68 (01101000) */ + {1, 4, 6, 7, 0, 0, 0, 0}, /* 0x69 (01101001) */ + {2, 4, 6, 7, 0, 0, 0, 0}, /* 0x6A (01101010) */ + {1, 2, 4, 6, 7, 0, 0, 0}, /* 0x6B (01101011) */ + {3, 4, 6, 7, 0, 0, 0, 0}, /* 0x6C (01101100) */ + {1, 3, 4, 6, 7, 0, 0, 0}, /* 0x6D (01101101) */ + {2, 3, 4, 6, 7, 0, 0, 0}, /* 0x6E (01101110) */ + {1, 2, 3, 4, 6, 7, 0, 0}, /* 0x6F (01101111) */ + {5, 6, 7, 0, 0, 0, 0, 0}, /* 0x70 (01110000) */ + {1, 5, 6, 7, 0, 0, 0, 0}, /* 0x71 (01110001) */ + {2, 5, 6, 7, 0, 0, 0, 0}, /* 0x72 (01110010) */ + {1, 2, 5, 6, 7, 0, 0, 0}, /* 0x73 (01110011) */ + {3, 5, 6, 7, 0, 0, 0, 0}, /* 0x74 (01110100) */ + {1, 3, 5, 6, 7, 0, 0, 0}, /* 0x75 (01110101) */ + {2, 3, 5, 6, 7, 0, 0, 0}, /* 0x76 (01110110) */ + {1, 2, 3, 5, 6, 7, 0, 0}, /* 0x77 (01110111) */ + {4, 5, 6, 7, 0, 0, 0, 0}, /* 0x78 (01111000) */ + {1, 4, 5, 6, 7, 0, 0, 0}, /* 0x79 (01111001) */ + {2, 4, 5, 6, 7, 0, 0, 0}, /* 0x7A (01111010) */ + {1, 2, 4, 5, 6, 7, 0, 0}, /* 0x7B (01111011) */ + {3, 4, 5, 6, 7, 0, 0, 0}, /* 0x7C (01111100) */ + {1, 3, 4, 5, 6, 7, 0, 0}, /* 0x7D (01111101) */ + {2, 3, 4, 5, 6, 7, 0, 0}, /* 0x7E (01111110) */ + {1, 2, 3, 4, 5, 6, 7, 0}, /* 0x7F (01111111) */ + {8, 0, 0, 0, 0, 0, 0, 0}, /* 0x80 (10000000) */ + {1, 8, 0, 0, 0, 0, 0, 0}, /* 0x81 (10000001) */ + {2, 8, 0, 0, 0, 0, 0, 0}, /* 0x82 (10000010) */ + {1, 2, 8, 0, 0, 0, 0, 0}, /* 0x83 (10000011) */ + {3, 8, 0, 0, 0, 0, 0, 0}, /* 0x84 (10000100) */ + {1, 3, 8, 0, 0, 0, 0, 0}, /* 0x85 (10000101) */ + {2, 3, 8, 0, 0, 0, 0, 0}, /* 0x86 (10000110) */ + {1, 2, 3, 8, 0, 0, 0, 0}, /* 0x87 (10000111) */ + {4, 8, 0, 0, 0, 0, 0, 0}, /* 0x88 (10001000) */ + {1, 4, 8, 0, 0, 0, 0, 0}, /* 0x89 (10001001) */ + {2, 4, 8, 0, 0, 0, 0, 0}, /* 0x8A (10001010) */ + {1, 2, 4, 8, 0, 0, 0, 0}, /* 0x8B (10001011) */ + {3, 4, 8, 0, 0, 0, 0, 0}, /* 0x8C (10001100) */ + {1, 3, 4, 8, 0, 0, 0, 0}, /* 0x8D (10001101) */ + {2, 3, 4, 8, 0, 0, 0, 0}, /* 0x8E (10001110) */ + {1, 2, 3, 4, 8, 0, 0, 0}, /* 0x8F (10001111) */ + {5, 8, 0, 0, 0, 0, 0, 0}, /* 0x90 (10010000) */ + {1, 5, 8, 0, 0, 0, 0, 0}, /* 0x91 (10010001) */ + {2, 5, 8, 0, 0, 0, 0, 0}, /* 0x92 (10010010) */ + {1, 2, 5, 8, 0, 0, 0, 0}, /* 0x93 (10010011) */ + {3, 5, 8, 0, 0, 0, 0, 0}, /* 0x94 (10010100) */ + {1, 3, 5, 8, 0, 0, 0, 0}, /* 0x95 (10010101) */ + {2, 3, 5, 8, 0, 0, 0, 0}, /* 0x96 (10010110) */ + {1, 2, 3, 5, 8, 0, 0, 0}, /* 0x97 (10010111) */ + {4, 5, 8, 0, 0, 0, 0, 0}, /* 0x98 (10011000) */ + {1, 4, 5, 8, 0, 0, 0, 0}, /* 0x99 (10011001) */ + {2, 4, 5, 8, 0, 0, 0, 0}, /* 0x9A (10011010) */ + {1, 2, 4, 5, 8, 0, 0, 0}, /* 0x9B (10011011) */ + {3, 4, 5, 8, 0, 0, 0, 0}, /* 0x9C (10011100) */ + {1, 3, 4, 5, 8, 0, 0, 0}, /* 0x9D (10011101) */ + {2, 3, 4, 5, 8, 0, 0, 0}, /* 0x9E (10011110) */ + {1, 2, 3, 4, 5, 8, 0, 0}, /* 0x9F (10011111) */ + {6, 8, 0, 0, 0, 0, 0, 0}, /* 0xA0 (10100000) */ + {1, 6, 8, 0, 0, 0, 0, 0}, /* 0xA1 (10100001) */ + {2, 6, 8, 0, 0, 0, 0, 0}, /* 0xA2 (10100010) */ + {1, 2, 6, 8, 0, 0, 0, 0}, /* 0xA3 (10100011) */ + {3, 6, 8, 0, 0, 0, 0, 0}, /* 0xA4 (10100100) */ + {1, 3, 6, 8, 0, 0, 0, 0}, /* 0xA5 (10100101) */ + {2, 3, 6, 8, 0, 0, 0, 0}, /* 0xA6 (10100110) */ + {1, 2, 3, 6, 8, 0, 0, 0}, /* 0xA7 (10100111) */ + {4, 6, 8, 0, 0, 0, 0, 0}, /* 0xA8 (10101000) */ + {1, 4, 6, 8, 0, 0, 0, 0}, /* 0xA9 (10101001) */ + {2, 4, 6, 8, 0, 0, 0, 0}, /* 0xAA (10101010) */ + {1, 2, 4, 6, 8, 0, 0, 0}, /* 0xAB (10101011) */ + {3, 4, 6, 8, 0, 0, 0, 0}, /* 0xAC (10101100) */ + {1, 3, 4, 6, 8, 0, 0, 0}, /* 0xAD (10101101) */ + {2, 3, 4, 6, 8, 0, 0, 0}, /* 0xAE (10101110) */ + {1, 2, 3, 4, 6, 8, 0, 0}, /* 0xAF (10101111) */ + {5, 6, 8, 0, 0, 0, 0, 0}, /* 0xB0 (10110000) */ + {1, 5, 6, 8, 0, 0, 0, 0}, /* 0xB1 (10110001) */ + {2, 5, 6, 8, 0, 0, 0, 0}, /* 0xB2 (10110010) */ + {1, 2, 5, 6, 8, 0, 0, 0}, /* 0xB3 (10110011) */ + {3, 5, 6, 8, 0, 0, 0, 0}, /* 0xB4 (10110100) */ + {1, 3, 5, 6, 8, 0, 0, 0}, /* 0xB5 (10110101) */ + {2, 3, 5, 6, 8, 0, 0, 0}, /* 0xB6 (10110110) */ + {1, 2, 3, 5, 6, 8, 0, 0}, /* 0xB7 (10110111) */ + {4, 5, 6, 8, 0, 0, 0, 0}, /* 0xB8 (10111000) */ + {1, 4, 5, 6, 8, 0, 0, 0}, /* 0xB9 (10111001) */ + {2, 4, 5, 6, 8, 0, 0, 0}, /* 0xBA (10111010) */ + {1, 2, 4, 5, 6, 8, 0, 0}, /* 0xBB (10111011) */ + {3, 4, 5, 6, 8, 0, 0, 0}, /* 0xBC (10111100) */ + {1, 3, 4, 5, 6, 8, 0, 0}, /* 0xBD (10111101) */ + {2, 3, 4, 5, 6, 8, 0, 0}, /* 0xBE (10111110) */ + {1, 2, 3, 4, 5, 6, 8, 0}, /* 0xBF (10111111) */ + {7, 8, 0, 0, 0, 0, 0, 0}, /* 0xC0 (11000000) */ + {1, 7, 8, 0, 0, 0, 0, 0}, /* 0xC1 (11000001) */ + {2, 7, 8, 0, 0, 0, 0, 0}, /* 0xC2 (11000010) */ + {1, 2, 7, 8, 0, 0, 0, 0}, /* 0xC3 (11000011) */ + {3, 7, 8, 0, 0, 0, 0, 0}, /* 0xC4 (11000100) */ + {1, 3, 7, 8, 0, 0, 0, 0}, /* 0xC5 (11000101) */ + {2, 3, 7, 8, 0, 0, 0, 0}, /* 0xC6 (11000110) */ + {1, 2, 3, 7, 8, 0, 0, 0}, /* 0xC7 (11000111) */ + {4, 7, 8, 0, 0, 0, 0, 0}, /* 0xC8 (11001000) */ + {1, 4, 7, 8, 0, 0, 0, 0}, /* 0xC9 (11001001) */ + {2, 4, 7, 8, 0, 0, 0, 0}, /* 0xCA (11001010) */ + {1, 2, 4, 7, 8, 0, 0, 0}, /* 0xCB (11001011) */ + {3, 4, 7, 8, 0, 0, 0, 0}, /* 0xCC (11001100) */ + {1, 3, 4, 7, 8, 0, 0, 0}, /* 0xCD (11001101) */ + {2, 3, 4, 7, 8, 0, 0, 0}, /* 0xCE (11001110) */ + {1, 2, 3, 4, 7, 8, 0, 0}, /* 0xCF (11001111) */ + {5, 7, 8, 0, 0, 0, 0, 0}, /* 0xD0 (11010000) */ + {1, 5, 7, 8, 0, 0, 0, 0}, /* 0xD1 (11010001) */ + {2, 5, 7, 8, 0, 0, 0, 0}, /* 0xD2 (11010010) */ + {1, 2, 5, 7, 8, 0, 0, 0}, /* 0xD3 (11010011) */ + {3, 5, 7, 8, 0, 0, 0, 0}, /* 0xD4 (11010100) */ + {1, 3, 5, 7, 8, 0, 0, 0}, /* 0xD5 (11010101) */ + {2, 3, 5, 7, 8, 0, 0, 0}, /* 0xD6 (11010110) */ + {1, 2, 3, 5, 7, 8, 0, 0}, /* 0xD7 (11010111) */ + {4, 5, 7, 8, 0, 0, 0, 0}, /* 0xD8 (11011000) */ + {1, 4, 5, 7, 8, 0, 0, 0}, /* 0xD9 (11011001) */ + {2, 4, 5, 7, 8, 0, 0, 0}, /* 0xDA (11011010) */ + {1, 2, 4, 5, 7, 8, 0, 0}, /* 0xDB (11011011) */ + {3, 4, 5, 7, 8, 0, 0, 0}, /* 0xDC (11011100) */ + {1, 3, 4, 5, 7, 8, 0, 0}, /* 0xDD (11011101) */ + {2, 3, 4, 5, 7, 8, 0, 0}, /* 0xDE (11011110) */ + {1, 2, 3, 4, 5, 7, 8, 0}, /* 0xDF (11011111) */ + {6, 7, 8, 0, 0, 0, 0, 0}, /* 0xE0 (11100000) */ + {1, 6, 7, 8, 0, 0, 0, 0}, /* 0xE1 (11100001) */ + {2, 6, 7, 8, 0, 0, 0, 0}, /* 0xE2 (11100010) */ + {1, 2, 6, 7, 8, 0, 0, 0}, /* 0xE3 (11100011) */ + {3, 6, 7, 8, 0, 0, 0, 0}, /* 0xE4 (11100100) */ + {1, 3, 6, 7, 8, 0, 0, 0}, /* 0xE5 (11100101) */ + {2, 3, 6, 7, 8, 0, 0, 0}, /* 0xE6 (11100110) */ + {1, 2, 3, 6, 7, 8, 0, 0}, /* 0xE7 (11100111) */ + {4, 6, 7, 8, 0, 0, 0, 0}, /* 0xE8 (11101000) */ + {1, 4, 6, 7, 8, 0, 0, 0}, /* 0xE9 (11101001) */ + {2, 4, 6, 7, 8, 0, 0, 0}, /* 0xEA (11101010) */ + {1, 2, 4, 6, 7, 8, 0, 0}, /* 0xEB (11101011) */ + {3, 4, 6, 7, 8, 0, 0, 0}, /* 0xEC (11101100) */ + {1, 3, 4, 6, 7, 8, 0, 0}, /* 0xED (11101101) */ + {2, 3, 4, 6, 7, 8, 0, 0}, /* 0xEE (11101110) */ + {1, 2, 3, 4, 6, 7, 8, 0}, /* 0xEF (11101111) */ + {5, 6, 7, 8, 0, 0, 0, 0}, /* 0xF0 (11110000) */ + {1, 5, 6, 7, 8, 0, 0, 0}, /* 0xF1 (11110001) */ + {2, 5, 6, 7, 8, 0, 0, 0}, /* 0xF2 (11110010) */ + {1, 2, 5, 6, 7, 8, 0, 0}, /* 0xF3 (11110011) */ + {3, 5, 6, 7, 8, 0, 0, 0}, /* 0xF4 (11110100) */ + {1, 3, 5, 6, 7, 8, 0, 0}, /* 0xF5 (11110101) */ + {2, 3, 5, 6, 7, 8, 0, 0}, /* 0xF6 (11110110) */ + {1, 2, 3, 5, 6, 7, 8, 0}, /* 0xF7 (11110111) */ + {4, 5, 6, 7, 8, 0, 0, 0}, /* 0xF8 (11111000) */ + {1, 4, 5, 6, 7, 8, 0, 0}, /* 0xF9 (11111001) */ + {2, 4, 5, 6, 7, 8, 0, 0}, /* 0xFA (11111010) */ + {1, 2, 4, 5, 6, 7, 8, 0}, /* 0xFB (11111011) */ + {3, 4, 5, 6, 7, 8, 0, 0}, /* 0xFC (11111100) */ + {1, 3, 4, 5, 6, 7, 8, 0}, /* 0xFD (11111101) */ + {2, 3, 4, 5, 6, 7, 8, 0}, /* 0xFE (11111110) */ + {1, 2, 3, 4, 5, 6, 7, 8} /* 0xFF (11111111) */ +}; + +#endif // #ifdef USEAVX + +#ifdef IS_X64 +// same as vecDecodeTable but in 16 bits +ALIGNED(32) +static uint16_t vecDecodeTable_uint16[256][8] = { + {0, 0, 0, 0, 0, 0, 0, 0}, /* 0x00 (00000000) */ + {1, 0, 0, 0, 0, 0, 0, 0}, /* 0x01 (00000001) */ + {2, 0, 0, 0, 0, 0, 0, 0}, /* 0x02 (00000010) */ + {1, 2, 0, 0, 0, 0, 0, 0}, /* 0x03 (00000011) */ + {3, 0, 0, 0, 0, 0, 0, 0}, /* 0x04 (00000100) */ + {1, 3, 0, 0, 0, 0, 0, 0}, /* 0x05 (00000101) */ + {2, 3, 0, 0, 0, 0, 0, 0}, /* 0x06 (00000110) */ + {1, 2, 3, 0, 0, 0, 0, 0}, /* 0x07 (00000111) */ + {4, 0, 0, 0, 0, 0, 0, 0}, /* 0x08 (00001000) */ + {1, 4, 0, 0, 0, 0, 0, 0}, /* 0x09 (00001001) */ + {2, 4, 0, 0, 0, 0, 0, 0}, /* 0x0A (00001010) */ + {1, 2, 4, 0, 0, 0, 0, 0}, /* 0x0B (00001011) */ + {3, 4, 0, 0, 0, 0, 0, 0}, /* 0x0C (00001100) */ + {1, 3, 4, 0, 0, 0, 0, 0}, /* 0x0D (00001101) */ + {2, 3, 4, 0, 0, 0, 0, 0}, /* 0x0E (00001110) */ + {1, 2, 3, 4, 0, 0, 0, 0}, /* 0x0F (00001111) */ + {5, 0, 0, 0, 0, 0, 0, 0}, /* 0x10 (00010000) */ + {1, 5, 0, 0, 0, 0, 0, 0}, /* 0x11 (00010001) */ + {2, 5, 0, 0, 0, 0, 0, 0}, /* 0x12 (00010010) */ + {1, 2, 5, 0, 0, 0, 0, 0}, /* 0x13 (00010011) */ + {3, 5, 0, 0, 0, 0, 0, 0}, /* 0x14 (00010100) */ + {1, 3, 5, 0, 0, 0, 0, 0}, /* 0x15 (00010101) */ + {2, 3, 5, 0, 0, 0, 0, 0}, /* 0x16 (00010110) */ + {1, 2, 3, 5, 0, 0, 0, 0}, /* 0x17 (00010111) */ + {4, 5, 0, 0, 0, 0, 0, 0}, /* 0x18 (00011000) */ + {1, 4, 5, 0, 0, 0, 0, 0}, /* 0x19 (00011001) */ + {2, 4, 5, 0, 0, 0, 0, 0}, /* 0x1A (00011010) */ + {1, 2, 4, 5, 0, 0, 0, 0}, /* 0x1B (00011011) */ + {3, 4, 5, 0, 0, 0, 0, 0}, /* 0x1C (00011100) */ + {1, 3, 4, 5, 0, 0, 0, 0}, /* 0x1D (00011101) */ + {2, 3, 4, 5, 0, 0, 0, 0}, /* 0x1E (00011110) */ + {1, 2, 3, 4, 5, 0, 0, 0}, /* 0x1F (00011111) */ + {6, 0, 0, 0, 0, 0, 0, 0}, /* 0x20 (00100000) */ + {1, 6, 0, 0, 0, 0, 0, 0}, /* 0x21 (00100001) */ + {2, 6, 0, 0, 0, 0, 0, 0}, /* 0x22 (00100010) */ + {1, 2, 6, 0, 0, 0, 0, 0}, /* 0x23 (00100011) */ + {3, 6, 0, 0, 0, 0, 0, 0}, /* 0x24 (00100100) */ + {1, 3, 6, 0, 0, 0, 0, 0}, /* 0x25 (00100101) */ + {2, 3, 6, 0, 0, 0, 0, 0}, /* 0x26 (00100110) */ + {1, 2, 3, 6, 0, 0, 0, 0}, /* 0x27 (00100111) */ + {4, 6, 0, 0, 0, 0, 0, 0}, /* 0x28 (00101000) */ + {1, 4, 6, 0, 0, 0, 0, 0}, /* 0x29 (00101001) */ + {2, 4, 6, 0, 0, 0, 0, 0}, /* 0x2A (00101010) */ + {1, 2, 4, 6, 0, 0, 0, 0}, /* 0x2B (00101011) */ + {3, 4, 6, 0, 0, 0, 0, 0}, /* 0x2C (00101100) */ + {1, 3, 4, 6, 0, 0, 0, 0}, /* 0x2D (00101101) */ + {2, 3, 4, 6, 0, 0, 0, 0}, /* 0x2E (00101110) */ + {1, 2, 3, 4, 6, 0, 0, 0}, /* 0x2F (00101111) */ + {5, 6, 0, 0, 0, 0, 0, 0}, /* 0x30 (00110000) */ + {1, 5, 6, 0, 0, 0, 0, 0}, /* 0x31 (00110001) */ + {2, 5, 6, 0, 0, 0, 0, 0}, /* 0x32 (00110010) */ + {1, 2, 5, 6, 0, 0, 0, 0}, /* 0x33 (00110011) */ + {3, 5, 6, 0, 0, 0, 0, 0}, /* 0x34 (00110100) */ + {1, 3, 5, 6, 0, 0, 0, 0}, /* 0x35 (00110101) */ + {2, 3, 5, 6, 0, 0, 0, 0}, /* 0x36 (00110110) */ + {1, 2, 3, 5, 6, 0, 0, 0}, /* 0x37 (00110111) */ + {4, 5, 6, 0, 0, 0, 0, 0}, /* 0x38 (00111000) */ + {1, 4, 5, 6, 0, 0, 0, 0}, /* 0x39 (00111001) */ + {2, 4, 5, 6, 0, 0, 0, 0}, /* 0x3A (00111010) */ + {1, 2, 4, 5, 6, 0, 0, 0}, /* 0x3B (00111011) */ + {3, 4, 5, 6, 0, 0, 0, 0}, /* 0x3C (00111100) */ + {1, 3, 4, 5, 6, 0, 0, 0}, /* 0x3D (00111101) */ + {2, 3, 4, 5, 6, 0, 0, 0}, /* 0x3E (00111110) */ + {1, 2, 3, 4, 5, 6, 0, 0}, /* 0x3F (00111111) */ + {7, 0, 0, 0, 0, 0, 0, 0}, /* 0x40 (01000000) */ + {1, 7, 0, 0, 0, 0, 0, 0}, /* 0x41 (01000001) */ + {2, 7, 0, 0, 0, 0, 0, 0}, /* 0x42 (01000010) */ + {1, 2, 7, 0, 0, 0, 0, 0}, /* 0x43 (01000011) */ + {3, 7, 0, 0, 0, 0, 0, 0}, /* 0x44 (01000100) */ + {1, 3, 7, 0, 0, 0, 0, 0}, /* 0x45 (01000101) */ + {2, 3, 7, 0, 0, 0, 0, 0}, /* 0x46 (01000110) */ + {1, 2, 3, 7, 0, 0, 0, 0}, /* 0x47 (01000111) */ + {4, 7, 0, 0, 0, 0, 0, 0}, /* 0x48 (01001000) */ + {1, 4, 7, 0, 0, 0, 0, 0}, /* 0x49 (01001001) */ + {2, 4, 7, 0, 0, 0, 0, 0}, /* 0x4A (01001010) */ + {1, 2, 4, 7, 0, 0, 0, 0}, /* 0x4B (01001011) */ + {3, 4, 7, 0, 0, 0, 0, 0}, /* 0x4C (01001100) */ + {1, 3, 4, 7, 0, 0, 0, 0}, /* 0x4D (01001101) */ + {2, 3, 4, 7, 0, 0, 0, 0}, /* 0x4E (01001110) */ + {1, 2, 3, 4, 7, 0, 0, 0}, /* 0x4F (01001111) */ + {5, 7, 0, 0, 0, 0, 0, 0}, /* 0x50 (01010000) */ + {1, 5, 7, 0, 0, 0, 0, 0}, /* 0x51 (01010001) */ + {2, 5, 7, 0, 0, 0, 0, 0}, /* 0x52 (01010010) */ + {1, 2, 5, 7, 0, 0, 0, 0}, /* 0x53 (01010011) */ + {3, 5, 7, 0, 0, 0, 0, 0}, /* 0x54 (01010100) */ + {1, 3, 5, 7, 0, 0, 0, 0}, /* 0x55 (01010101) */ + {2, 3, 5, 7, 0, 0, 0, 0}, /* 0x56 (01010110) */ + {1, 2, 3, 5, 7, 0, 0, 0}, /* 0x57 (01010111) */ + {4, 5, 7, 0, 0, 0, 0, 0}, /* 0x58 (01011000) */ + {1, 4, 5, 7, 0, 0, 0, 0}, /* 0x59 (01011001) */ + {2, 4, 5, 7, 0, 0, 0, 0}, /* 0x5A (01011010) */ + {1, 2, 4, 5, 7, 0, 0, 0}, /* 0x5B (01011011) */ + {3, 4, 5, 7, 0, 0, 0, 0}, /* 0x5C (01011100) */ + {1, 3, 4, 5, 7, 0, 0, 0}, /* 0x5D (01011101) */ + {2, 3, 4, 5, 7, 0, 0, 0}, /* 0x5E (01011110) */ + {1, 2, 3, 4, 5, 7, 0, 0}, /* 0x5F (01011111) */ + {6, 7, 0, 0, 0, 0, 0, 0}, /* 0x60 (01100000) */ + {1, 6, 7, 0, 0, 0, 0, 0}, /* 0x61 (01100001) */ + {2, 6, 7, 0, 0, 0, 0, 0}, /* 0x62 (01100010) */ + {1, 2, 6, 7, 0, 0, 0, 0}, /* 0x63 (01100011) */ + {3, 6, 7, 0, 0, 0, 0, 0}, /* 0x64 (01100100) */ + {1, 3, 6, 7, 0, 0, 0, 0}, /* 0x65 (01100101) */ + {2, 3, 6, 7, 0, 0, 0, 0}, /* 0x66 (01100110) */ + {1, 2, 3, 6, 7, 0, 0, 0}, /* 0x67 (01100111) */ + {4, 6, 7, 0, 0, 0, 0, 0}, /* 0x68 (01101000) */ + {1, 4, 6, 7, 0, 0, 0, 0}, /* 0x69 (01101001) */ + {2, 4, 6, 7, 0, 0, 0, 0}, /* 0x6A (01101010) */ + {1, 2, 4, 6, 7, 0, 0, 0}, /* 0x6B (01101011) */ + {3, 4, 6, 7, 0, 0, 0, 0}, /* 0x6C (01101100) */ + {1, 3, 4, 6, 7, 0, 0, 0}, /* 0x6D (01101101) */ + {2, 3, 4, 6, 7, 0, 0, 0}, /* 0x6E (01101110) */ + {1, 2, 3, 4, 6, 7, 0, 0}, /* 0x6F (01101111) */ + {5, 6, 7, 0, 0, 0, 0, 0}, /* 0x70 (01110000) */ + {1, 5, 6, 7, 0, 0, 0, 0}, /* 0x71 (01110001) */ + {2, 5, 6, 7, 0, 0, 0, 0}, /* 0x72 (01110010) */ + {1, 2, 5, 6, 7, 0, 0, 0}, /* 0x73 (01110011) */ + {3, 5, 6, 7, 0, 0, 0, 0}, /* 0x74 (01110100) */ + {1, 3, 5, 6, 7, 0, 0, 0}, /* 0x75 (01110101) */ + {2, 3, 5, 6, 7, 0, 0, 0}, /* 0x76 (01110110) */ + {1, 2, 3, 5, 6, 7, 0, 0}, /* 0x77 (01110111) */ + {4, 5, 6, 7, 0, 0, 0, 0}, /* 0x78 (01111000) */ + {1, 4, 5, 6, 7, 0, 0, 0}, /* 0x79 (01111001) */ + {2, 4, 5, 6, 7, 0, 0, 0}, /* 0x7A (01111010) */ + {1, 2, 4, 5, 6, 7, 0, 0}, /* 0x7B (01111011) */ + {3, 4, 5, 6, 7, 0, 0, 0}, /* 0x7C (01111100) */ + {1, 3, 4, 5, 6, 7, 0, 0}, /* 0x7D (01111101) */ + {2, 3, 4, 5, 6, 7, 0, 0}, /* 0x7E (01111110) */ + {1, 2, 3, 4, 5, 6, 7, 0}, /* 0x7F (01111111) */ + {8, 0, 0, 0, 0, 0, 0, 0}, /* 0x80 (10000000) */ + {1, 8, 0, 0, 0, 0, 0, 0}, /* 0x81 (10000001) */ + {2, 8, 0, 0, 0, 0, 0, 0}, /* 0x82 (10000010) */ + {1, 2, 8, 0, 0, 0, 0, 0}, /* 0x83 (10000011) */ + {3, 8, 0, 0, 0, 0, 0, 0}, /* 0x84 (10000100) */ + {1, 3, 8, 0, 0, 0, 0, 0}, /* 0x85 (10000101) */ + {2, 3, 8, 0, 0, 0, 0, 0}, /* 0x86 (10000110) */ + {1, 2, 3, 8, 0, 0, 0, 0}, /* 0x87 (10000111) */ + {4, 8, 0, 0, 0, 0, 0, 0}, /* 0x88 (10001000) */ + {1, 4, 8, 0, 0, 0, 0, 0}, /* 0x89 (10001001) */ + {2, 4, 8, 0, 0, 0, 0, 0}, /* 0x8A (10001010) */ + {1, 2, 4, 8, 0, 0, 0, 0}, /* 0x8B (10001011) */ + {3, 4, 8, 0, 0, 0, 0, 0}, /* 0x8C (10001100) */ + {1, 3, 4, 8, 0, 0, 0, 0}, /* 0x8D (10001101) */ + {2, 3, 4, 8, 0, 0, 0, 0}, /* 0x8E (10001110) */ + {1, 2, 3, 4, 8, 0, 0, 0}, /* 0x8F (10001111) */ + {5, 8, 0, 0, 0, 0, 0, 0}, /* 0x90 (10010000) */ + {1, 5, 8, 0, 0, 0, 0, 0}, /* 0x91 (10010001) */ + {2, 5, 8, 0, 0, 0, 0, 0}, /* 0x92 (10010010) */ + {1, 2, 5, 8, 0, 0, 0, 0}, /* 0x93 (10010011) */ + {3, 5, 8, 0, 0, 0, 0, 0}, /* 0x94 (10010100) */ + {1, 3, 5, 8, 0, 0, 0, 0}, /* 0x95 (10010101) */ + {2, 3, 5, 8, 0, 0, 0, 0}, /* 0x96 (10010110) */ + {1, 2, 3, 5, 8, 0, 0, 0}, /* 0x97 (10010111) */ + {4, 5, 8, 0, 0, 0, 0, 0}, /* 0x98 (10011000) */ + {1, 4, 5, 8, 0, 0, 0, 0}, /* 0x99 (10011001) */ + {2, 4, 5, 8, 0, 0, 0, 0}, /* 0x9A (10011010) */ + {1, 2, 4, 5, 8, 0, 0, 0}, /* 0x9B (10011011) */ + {3, 4, 5, 8, 0, 0, 0, 0}, /* 0x9C (10011100) */ + {1, 3, 4, 5, 8, 0, 0, 0}, /* 0x9D (10011101) */ + {2, 3, 4, 5, 8, 0, 0, 0}, /* 0x9E (10011110) */ + {1, 2, 3, 4, 5, 8, 0, 0}, /* 0x9F (10011111) */ + {6, 8, 0, 0, 0, 0, 0, 0}, /* 0xA0 (10100000) */ + {1, 6, 8, 0, 0, 0, 0, 0}, /* 0xA1 (10100001) */ + {2, 6, 8, 0, 0, 0, 0, 0}, /* 0xA2 (10100010) */ + {1, 2, 6, 8, 0, 0, 0, 0}, /* 0xA3 (10100011) */ + {3, 6, 8, 0, 0, 0, 0, 0}, /* 0xA4 (10100100) */ + {1, 3, 6, 8, 0, 0, 0, 0}, /* 0xA5 (10100101) */ + {2, 3, 6, 8, 0, 0, 0, 0}, /* 0xA6 (10100110) */ + {1, 2, 3, 6, 8, 0, 0, 0}, /* 0xA7 (10100111) */ + {4, 6, 8, 0, 0, 0, 0, 0}, /* 0xA8 (10101000) */ + {1, 4, 6, 8, 0, 0, 0, 0}, /* 0xA9 (10101001) */ + {2, 4, 6, 8, 0, 0, 0, 0}, /* 0xAA (10101010) */ + {1, 2, 4, 6, 8, 0, 0, 0}, /* 0xAB (10101011) */ + {3, 4, 6, 8, 0, 0, 0, 0}, /* 0xAC (10101100) */ + {1, 3, 4, 6, 8, 0, 0, 0}, /* 0xAD (10101101) */ + {2, 3, 4, 6, 8, 0, 0, 0}, /* 0xAE (10101110) */ + {1, 2, 3, 4, 6, 8, 0, 0}, /* 0xAF (10101111) */ + {5, 6, 8, 0, 0, 0, 0, 0}, /* 0xB0 (10110000) */ + {1, 5, 6, 8, 0, 0, 0, 0}, /* 0xB1 (10110001) */ + {2, 5, 6, 8, 0, 0, 0, 0}, /* 0xB2 (10110010) */ + {1, 2, 5, 6, 8, 0, 0, 0}, /* 0xB3 (10110011) */ + {3, 5, 6, 8, 0, 0, 0, 0}, /* 0xB4 (10110100) */ + {1, 3, 5, 6, 8, 0, 0, 0}, /* 0xB5 (10110101) */ + {2, 3, 5, 6, 8, 0, 0, 0}, /* 0xB6 (10110110) */ + {1, 2, 3, 5, 6, 8, 0, 0}, /* 0xB7 (10110111) */ + {4, 5, 6, 8, 0, 0, 0, 0}, /* 0xB8 (10111000) */ + {1, 4, 5, 6, 8, 0, 0, 0}, /* 0xB9 (10111001) */ + {2, 4, 5, 6, 8, 0, 0, 0}, /* 0xBA (10111010) */ + {1, 2, 4, 5, 6, 8, 0, 0}, /* 0xBB (10111011) */ + {3, 4, 5, 6, 8, 0, 0, 0}, /* 0xBC (10111100) */ + {1, 3, 4, 5, 6, 8, 0, 0}, /* 0xBD (10111101) */ + {2, 3, 4, 5, 6, 8, 0, 0}, /* 0xBE (10111110) */ + {1, 2, 3, 4, 5, 6, 8, 0}, /* 0xBF (10111111) */ + {7, 8, 0, 0, 0, 0, 0, 0}, /* 0xC0 (11000000) */ + {1, 7, 8, 0, 0, 0, 0, 0}, /* 0xC1 (11000001) */ + {2, 7, 8, 0, 0, 0, 0, 0}, /* 0xC2 (11000010) */ + {1, 2, 7, 8, 0, 0, 0, 0}, /* 0xC3 (11000011) */ + {3, 7, 8, 0, 0, 0, 0, 0}, /* 0xC4 (11000100) */ + {1, 3, 7, 8, 0, 0, 0, 0}, /* 0xC5 (11000101) */ + {2, 3, 7, 8, 0, 0, 0, 0}, /* 0xC6 (11000110) */ + {1, 2, 3, 7, 8, 0, 0, 0}, /* 0xC7 (11000111) */ + {4, 7, 8, 0, 0, 0, 0, 0}, /* 0xC8 (11001000) */ + {1, 4, 7, 8, 0, 0, 0, 0}, /* 0xC9 (11001001) */ + {2, 4, 7, 8, 0, 0, 0, 0}, /* 0xCA (11001010) */ + {1, 2, 4, 7, 8, 0, 0, 0}, /* 0xCB (11001011) */ + {3, 4, 7, 8, 0, 0, 0, 0}, /* 0xCC (11001100) */ + {1, 3, 4, 7, 8, 0, 0, 0}, /* 0xCD (11001101) */ + {2, 3, 4, 7, 8, 0, 0, 0}, /* 0xCE (11001110) */ + {1, 2, 3, 4, 7, 8, 0, 0}, /* 0xCF (11001111) */ + {5, 7, 8, 0, 0, 0, 0, 0}, /* 0xD0 (11010000) */ + {1, 5, 7, 8, 0, 0, 0, 0}, /* 0xD1 (11010001) */ + {2, 5, 7, 8, 0, 0, 0, 0}, /* 0xD2 (11010010) */ + {1, 2, 5, 7, 8, 0, 0, 0}, /* 0xD3 (11010011) */ + {3, 5, 7, 8, 0, 0, 0, 0}, /* 0xD4 (11010100) */ + {1, 3, 5, 7, 8, 0, 0, 0}, /* 0xD5 (11010101) */ + {2, 3, 5, 7, 8, 0, 0, 0}, /* 0xD6 (11010110) */ + {1, 2, 3, 5, 7, 8, 0, 0}, /* 0xD7 (11010111) */ + {4, 5, 7, 8, 0, 0, 0, 0}, /* 0xD8 (11011000) */ + {1, 4, 5, 7, 8, 0, 0, 0}, /* 0xD9 (11011001) */ + {2, 4, 5, 7, 8, 0, 0, 0}, /* 0xDA (11011010) */ + {1, 2, 4, 5, 7, 8, 0, 0}, /* 0xDB (11011011) */ + {3, 4, 5, 7, 8, 0, 0, 0}, /* 0xDC (11011100) */ + {1, 3, 4, 5, 7, 8, 0, 0}, /* 0xDD (11011101) */ + {2, 3, 4, 5, 7, 8, 0, 0}, /* 0xDE (11011110) */ + {1, 2, 3, 4, 5, 7, 8, 0}, /* 0xDF (11011111) */ + {6, 7, 8, 0, 0, 0, 0, 0}, /* 0xE0 (11100000) */ + {1, 6, 7, 8, 0, 0, 0, 0}, /* 0xE1 (11100001) */ + {2, 6, 7, 8, 0, 0, 0, 0}, /* 0xE2 (11100010) */ + {1, 2, 6, 7, 8, 0, 0, 0}, /* 0xE3 (11100011) */ + {3, 6, 7, 8, 0, 0, 0, 0}, /* 0xE4 (11100100) */ + {1, 3, 6, 7, 8, 0, 0, 0}, /* 0xE5 (11100101) */ + {2, 3, 6, 7, 8, 0, 0, 0}, /* 0xE6 (11100110) */ + {1, 2, 3, 6, 7, 8, 0, 0}, /* 0xE7 (11100111) */ + {4, 6, 7, 8, 0, 0, 0, 0}, /* 0xE8 (11101000) */ + {1, 4, 6, 7, 8, 0, 0, 0}, /* 0xE9 (11101001) */ + {2, 4, 6, 7, 8, 0, 0, 0}, /* 0xEA (11101010) */ + {1, 2, 4, 6, 7, 8, 0, 0}, /* 0xEB (11101011) */ + {3, 4, 6, 7, 8, 0, 0, 0}, /* 0xEC (11101100) */ + {1, 3, 4, 6, 7, 8, 0, 0}, /* 0xED (11101101) */ + {2, 3, 4, 6, 7, 8, 0, 0}, /* 0xEE (11101110) */ + {1, 2, 3, 4, 6, 7, 8, 0}, /* 0xEF (11101111) */ + {5, 6, 7, 8, 0, 0, 0, 0}, /* 0xF0 (11110000) */ + {1, 5, 6, 7, 8, 0, 0, 0}, /* 0xF1 (11110001) */ + {2, 5, 6, 7, 8, 0, 0, 0}, /* 0xF2 (11110010) */ + {1, 2, 5, 6, 7, 8, 0, 0}, /* 0xF3 (11110011) */ + {3, 5, 6, 7, 8, 0, 0, 0}, /* 0xF4 (11110100) */ + {1, 3, 5, 6, 7, 8, 0, 0}, /* 0xF5 (11110101) */ + {2, 3, 5, 6, 7, 8, 0, 0}, /* 0xF6 (11110110) */ + {1, 2, 3, 5, 6, 7, 8, 0}, /* 0xF7 (11110111) */ + {4, 5, 6, 7, 8, 0, 0, 0}, /* 0xF8 (11111000) */ + {1, 4, 5, 6, 7, 8, 0, 0}, /* 0xF9 (11111001) */ + {2, 4, 5, 6, 7, 8, 0, 0}, /* 0xFA (11111010) */ + {1, 2, 4, 5, 6, 7, 8, 0}, /* 0xFB (11111011) */ + {3, 4, 5, 6, 7, 8, 0, 0}, /* 0xFC (11111100) */ + {1, 3, 4, 5, 6, 7, 8, 0}, /* 0xFD (11111101) */ + {2, 3, 4, 5, 6, 7, 8, 0}, /* 0xFE (11111110) */ + {1, 2, 3, 4, 5, 6, 7, 8} /* 0xFF (11111111) */ +}; + +#endif + +#ifdef USEAVX + +size_t bitset_extract_setbits_avx2(uint64_t *array, size_t length, void *vout, + size_t outcapacity, uint32_t base) { + uint32_t *out = (uint32_t *)vout; + uint32_t *initout = out; + __m256i baseVec = _mm256_set1_epi32(base - 1); + __m256i incVec = _mm256_set1_epi32(64); + __m256i add8 = _mm256_set1_epi32(8); + uint32_t *safeout = out + outcapacity; + size_t i = 0; + for (; (i < length) && (out + 64 <= safeout); ++i) { + uint64_t w = array[i]; + if (w == 0) { + baseVec = _mm256_add_epi32(baseVec, incVec); + } else { + for (int k = 0; k < 4; ++k) { + uint8_t byteA = (uint8_t)w; + uint8_t byteB = (uint8_t)(w >> 8); + w >>= 16; + __m256i vecA = + _mm256_load_si256((const __m256i *)vecDecodeTable[byteA]); + __m256i vecB = + _mm256_load_si256((const __m256i *)vecDecodeTable[byteB]); + uint8_t advanceA = lengthTable[byteA]; + uint8_t advanceB = lengthTable[byteB]; + vecA = _mm256_add_epi32(baseVec, vecA); + baseVec = _mm256_add_epi32(baseVec, add8); + vecB = _mm256_add_epi32(baseVec, vecB); + baseVec = _mm256_add_epi32(baseVec, add8); + _mm256_storeu_si256((__m256i *)out, vecA); + out += advanceA; + _mm256_storeu_si256((__m256i *)out, vecB); + out += advanceB; + } + } + } + base += i * 64; + for (; (i < length) && (out < safeout); ++i) { + uint64_t w = array[i]; + while ((w != 0) && (out < safeout)) { + uint64_t t = w & (~w + 1); // on x64, should compile to BLSI (careful: the Intel compiler seems to fail) + int r = __builtin_ctzll(w); // on x64, should compile to TZCNT + uint32_t val = r + base; + memcpy(out, &val, + sizeof(uint32_t)); // should be compiled as a MOV on x64 + out++; + w ^= t; + } + base += 64; + } + return out - initout; +} +#endif // USEAVX + +size_t bitset_extract_setbits(uint64_t *bitset, size_t length, void *vout, + uint32_t base) { + int outpos = 0; + uint32_t *out = (uint32_t *)vout; + for (size_t i = 0; i < length; ++i) { + uint64_t w = bitset[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); // on x64, should compile to BLSI (careful: the Intel compiler seems to fail) + int r = __builtin_ctzll(w); // on x64, should compile to TZCNT + uint32_t val = r + base; + memcpy(out + outpos, &val, + sizeof(uint32_t)); // should be compiled as a MOV on x64 + outpos++; + w ^= t; + } + base += 64; + } + return outpos; +} + +size_t bitset_extract_intersection_setbits_uint16(const uint64_t * __restrict__ bitset1, + const uint64_t * __restrict__ bitset2, + size_t length, uint16_t *out, + uint16_t base) { + int outpos = 0; + for (size_t i = 0; i < length; ++i) { + uint64_t w = bitset1[i] & bitset2[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + out[outpos++] = r + base; + w ^= t; + } + base += 64; + } + return outpos; +} + +#ifdef IS_X64 +/* + * Given a bitset containing "length" 64-bit words, write out the position + * of all the set bits to "out" as 16-bit integers, values start at "base" (can + *be set to zero). + * + * The "out" pointer should be sufficient to store the actual number of bits + *set. + * + * Returns how many values were actually decoded. + * + * This function uses SSE decoding. + */ +size_t bitset_extract_setbits_sse_uint16(const uint64_t *bitset, size_t length, + uint16_t *out, size_t outcapacity, + uint16_t base) { + uint16_t *initout = out; + __m128i baseVec = _mm_set1_epi16(base - 1); + __m128i incVec = _mm_set1_epi16(64); + __m128i add8 = _mm_set1_epi16(8); + uint16_t *safeout = out + outcapacity; + const int numberofbytes = 2; // process two bytes at a time + size_t i = 0; + for (; (i < length) && (out + numberofbytes * 8 <= safeout); ++i) { + uint64_t w = bitset[i]; + if (w == 0) { + baseVec = _mm_add_epi16(baseVec, incVec); + } else { + for (int k = 0; k < 4; ++k) { + uint8_t byteA = (uint8_t)w; + uint8_t byteB = (uint8_t)(w >> 8); + w >>= 16; + __m128i vecA = _mm_load_si128( + (const __m128i *)vecDecodeTable_uint16[byteA]); + __m128i vecB = _mm_load_si128( + (const __m128i *)vecDecodeTable_uint16[byteB]); + uint8_t advanceA = lengthTable[byteA]; + uint8_t advanceB = lengthTable[byteB]; + vecA = _mm_add_epi16(baseVec, vecA); + baseVec = _mm_add_epi16(baseVec, add8); + vecB = _mm_add_epi16(baseVec, vecB); + baseVec = _mm_add_epi16(baseVec, add8); + _mm_storeu_si128((__m128i *)out, vecA); + out += advanceA; + _mm_storeu_si128((__m128i *)out, vecB); + out += advanceB; + } + } + } + base += (uint16_t)(i * 64); + for (; (i < length) && (out < safeout); ++i) { + uint64_t w = bitset[i]; + while ((w != 0) && (out < safeout)) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + *out = r + base; + out++; + w ^= t; + } + base += 64; + } + return out - initout; +} +#endif + +/* + * Given a bitset containing "length" 64-bit words, write out the position + * of all the set bits to "out", values start at "base" (can be set to zero). + * + * The "out" pointer should be sufficient to store the actual number of bits + *set. + * + * Returns how many values were actually decoded. + */ +size_t bitset_extract_setbits_uint16(const uint64_t *bitset, size_t length, + uint16_t *out, uint16_t base) { + int outpos = 0; + for (size_t i = 0; i < length; ++i) { + uint64_t w = bitset[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + out[outpos++] = r + base; + w ^= t; + } + base += 64; + } + return outpos; +} + +#if defined(ASMBITMANIPOPTIMIZATION) + +uint64_t bitset_set_list_withcard(void *bitset, uint64_t card, + const uint16_t *list, uint64_t length) { + uint64_t offset, load, pos; + uint64_t shift = 6; + const uint16_t *end = list + length; + if (!length) return card; + // TODO: could unroll for performance, see bitset_set_list + // bts is not available as an intrinsic in GCC + __asm volatile( + "1:\n" + "movzwq (%[list]), %[pos]\n" + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)\n" + "sbb $-1, %[card]\n" + "add $2, %[list]\n" + "cmp %[list], %[end]\n" + "jnz 1b" + : [card] "+&r"(card), [list] "+&r"(list), [load] "=&r"(load), + [pos] "=&r"(pos), [offset] "=&r"(offset) + : [end] "r"(end), [bitset] "r"(bitset), [shift] "r"(shift)); + return card; +} + +void bitset_set_list(void *bitset, const uint16_t *list, uint64_t length) { + uint64_t pos; + const uint16_t *end = list + length; + + uint64_t shift = 6; + uint64_t offset; + uint64_t load; + for (; list + 3 < end; list += 4) { + pos = list[0]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [load] "=&r"(load), [offset] "=&r"(offset) + : [bitset] "r"(bitset), [shift] "r"(shift), [pos] "r"(pos)); + pos = list[1]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [load] "=&r"(load), [offset] "=&r"(offset) + : [bitset] "r"(bitset), [shift] "r"(shift), [pos] "r"(pos)); + pos = list[2]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [load] "=&r"(load), [offset] "=&r"(offset) + : [bitset] "r"(bitset), [shift] "r"(shift), [pos] "r"(pos)); + pos = list[3]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [load] "=&r"(load), [offset] "=&r"(offset) + : [bitset] "r"(bitset), [shift] "r"(shift), [pos] "r"(pos)); + } + + while (list != end) { + pos = list[0]; + __asm volatile( + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "bts %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)" + : [load] "=&r"(load), [offset] "=&r"(offset) + : [bitset] "r"(bitset), [shift] "r"(shift), [pos] "r"(pos)); + list++; + } +} + +uint64_t bitset_clear_list(void *bitset, uint64_t card, const uint16_t *list, + uint64_t length) { + uint64_t offset, load, pos; + uint64_t shift = 6; + const uint16_t *end = list + length; + if (!length) return card; + // btr is not available as an intrinsic in GCC + __asm volatile( + "1:\n" + "movzwq (%[list]), %[pos]\n" + "shrx %[shift], %[pos], %[offset]\n" + "mov (%[bitset],%[offset],8), %[load]\n" + "btr %[pos], %[load]\n" + "mov %[load], (%[bitset],%[offset],8)\n" + "sbb $0, %[card]\n" + "add $2, %[list]\n" + "cmp %[list], %[end]\n" + "jnz 1b" + : [card] "+&r"(card), [list] "+&r"(list), [load] "=&r"(load), + [pos] "=&r"(pos), [offset] "=&r"(offset) + : [end] "r"(end), [bitset] "r"(bitset), [shift] "r"(shift) + : + /* clobbers */ "memory"); + return card; +} + +#else +uint64_t bitset_clear_list(void *bitset, uint64_t card, const uint16_t *list, + uint64_t length) { + uint64_t offset, load, newload, pos, index; + const uint16_t *end = list + length; + while (list != end) { + pos = *(const uint16_t *)list; + offset = pos >> 6; + index = pos % 64; + load = ((uint64_t *)bitset)[offset]; + newload = load & ~(UINT64_C(1) << index); + card -= (load ^ newload) >> index; + ((uint64_t *)bitset)[offset] = newload; + list++; + } + return card; +} + +uint64_t bitset_set_list_withcard(void *bitset, uint64_t card, + const uint16_t *list, uint64_t length) { + uint64_t offset, load, newload, pos, index; + const uint16_t *end = list + length; + while (list != end) { + pos = *(const uint16_t *)list; + offset = pos >> 6; + index = pos % 64; + load = ((uint64_t *)bitset)[offset]; + newload = load | (UINT64_C(1) << index); + card += (load ^ newload) >> index; + ((uint64_t *)bitset)[offset] = newload; + list++; + } + return card; +} + +void bitset_set_list(void *bitset, const uint16_t *list, uint64_t length) { + uint64_t offset, load, newload, pos, index; + const uint16_t *end = list + length; + while (list != end) { + pos = *(const uint16_t *)list; + offset = pos >> 6; + index = pos % 64; + load = ((uint64_t *)bitset)[offset]; + newload = load | (UINT64_C(1) << index); + ((uint64_t *)bitset)[offset] = newload; + list++; + } +} + +#endif + +/* flip specified bits */ +/* TODO: consider whether worthwhile to make an asm version */ + +uint64_t bitset_flip_list_withcard(void *bitset, uint64_t card, + const uint16_t *list, uint64_t length) { + uint64_t offset, load, newload, pos, index; + const uint16_t *end = list + length; + while (list != end) { + pos = *(const uint16_t *)list; + offset = pos >> 6; + index = pos % 64; + load = ((uint64_t *)bitset)[offset]; + newload = load ^ (UINT64_C(1) << index); + // todo: is a branch here all that bad? + card += + (1 - 2 * (((UINT64_C(1) << index) & load) >> index)); // +1 or -1 + ((uint64_t *)bitset)[offset] = newload; + list++; + } + return card; +} + +void bitset_flip_list(void *bitset, const uint16_t *list, uint64_t length) { + uint64_t offset, load, newload, pos, index; + const uint16_t *end = list + length; + while (list != end) { + pos = *(const uint16_t *)list; + offset = pos >> 6; + index = pos % 64; + load = ((uint64_t *)bitset)[offset]; + newload = load ^ (UINT64_C(1) << index); + ((uint64_t *)bitset)[offset] = newload; + list++; + } +} +/* end file src/bitset_util.c */ +/* begin file src/containers/array.c */ +/* + * array.c + * + */ + +#include +#include +#include + +/* Create a new array with capacity size. Return NULL in case of failure. */ +array_container_t *array_container_create_given_capacity(int32_t size) { + array_container_t *container; + + container = (array_container_t *)malloc(sizeof(array_container_t)); + assert (container); + + if( size <= 0 ) { // we don't want to rely on malloc(0) + container->array = NULL; + } else { + container->array = (uint16_t *)malloc(sizeof(uint16_t) * size); + assert (container->array); + } + + container->capacity = size; + container->cardinality = 0; + + return container; +} + +/* Create a new array. Return NULL in case of failure. */ +array_container_t *array_container_create(void) { + return array_container_create_given_capacity(ARRAY_DEFAULT_INIT_SIZE); +} + +/* Create a new array containing all values in [min,max). */ +array_container_t * array_container_create_range(uint32_t min, uint32_t max) { + array_container_t * answer = array_container_create_given_capacity(max - min + 1); + if(answer == NULL) return answer; + answer->cardinality = 0; + for(uint32_t k = min; k < max; k++) { + answer->array[answer->cardinality++] = k; + } + return answer; +} + +/* Duplicate container */ +array_container_t *array_container_clone(const array_container_t *src) { + array_container_t *newcontainer = + array_container_create_given_capacity(src->capacity); + if (newcontainer == NULL) return NULL; + + newcontainer->cardinality = src->cardinality; + + memcpy(newcontainer->array, src->array, + src->cardinality * sizeof(uint16_t)); + + return newcontainer; +} + +int array_container_shrink_to_fit(array_container_t *src) { + if (src->cardinality == src->capacity) return 0; // nothing to do + int savings = src->capacity - src->cardinality; + src->capacity = src->cardinality; + if( src->capacity == 0) { // we do not want to rely on realloc for zero allocs + free(src->array); + src->array = NULL; + } else { + uint16_t *oldarray = src->array; + src->array = + (uint16_t *)realloc(oldarray, src->capacity * sizeof(uint16_t)); + } + return savings; +} + +/* Free memory. */ +void array_container_free(array_container_t *arr) { + if(arr->array != NULL) {// Jon Strabala reports that some tools complain otherwise + free(arr->array); + arr->array = NULL; // pedantic + } + free(arr); +} + +static inline int32_t grow_capacity(int32_t capacity) { + return (capacity <= 0) ? ARRAY_DEFAULT_INIT_SIZE + : capacity < 64 ? capacity * 2 + : capacity < 1024 ? capacity * 3 / 2 + : capacity * 5 / 4; +} + +static inline int32_t clamp(int32_t val, int32_t min, int32_t max) { + return ((val < min) ? min : (val > max) ? max : val); +} + +void array_container_grow(array_container_t *container, int32_t min, + bool preserve) { + + int32_t max = (min <= DEFAULT_MAX_SIZE ? DEFAULT_MAX_SIZE : 65536); + int32_t new_capacity = clamp(grow_capacity(container->capacity), min, max); + + container->capacity = new_capacity; + uint16_t *array = container->array; + + if (preserve) { + container->array = + (uint16_t *)realloc(array, new_capacity * sizeof(uint16_t)); + } else { + // Jon Strabala reports that some tools complain otherwise + if (array != NULL) { + free(array); + } + container->array = (uint16_t *)malloc(new_capacity * sizeof(uint16_t)); + } + + // handle the case where realloc fails + if (container->array == NULL) { + fprintf(stderr, "could not allocate memory\n"); + } + assert(container->array != NULL); +} + +/* Copy one container into another. We assume that they are distinct. */ +void array_container_copy(const array_container_t *src, + array_container_t *dst) { + const int32_t cardinality = src->cardinality; + if (cardinality > dst->capacity) { + array_container_grow(dst, cardinality, false); + } + + dst->cardinality = cardinality; + memcpy(dst->array, src->array, cardinality * sizeof(uint16_t)); +} + +void array_container_add_from_range(array_container_t *arr, uint32_t min, + uint32_t max, uint16_t step) { + for (uint32_t value = min; value < max; value += step) { + array_container_append(arr, value); + } +} + +/* Computes the union of array1 and array2 and write the result to arrayout. + * It is assumed that arrayout is distinct from both array1 and array2. + */ +void array_container_union(const array_container_t *array_1, + const array_container_t *array_2, + array_container_t *out) { + const int32_t card_1 = array_1->cardinality, card_2 = array_2->cardinality; + const int32_t max_cardinality = card_1 + card_2; + + if (out->capacity < max_cardinality) { + array_container_grow(out, max_cardinality, false); + } + out->cardinality = (int32_t)fast_union_uint16(array_1->array, card_1, + array_2->array, card_2, out->array); + +} + +/* Computes the difference of array1 and array2 and write the result + * to array out. + * Array out does not need to be distinct from array_1 + */ +void array_container_andnot(const array_container_t *array_1, + const array_container_t *array_2, + array_container_t *out) { + if (out->capacity < array_1->cardinality) + array_container_grow(out, array_1->cardinality, false); +#ifdef ROARING_VECTOR_OPERATIONS_ENABLED + if((out != array_1) && (out != array_2)) { + out->cardinality = + difference_vector16(array_1->array, array_1->cardinality, + array_2->array, array_2->cardinality, out->array); + } else { + out->cardinality = + difference_uint16(array_1->array, array_1->cardinality, array_2->array, + array_2->cardinality, out->array); + } +#else + out->cardinality = + difference_uint16(array_1->array, array_1->cardinality, array_2->array, + array_2->cardinality, out->array); +#endif +} + +/* Computes the symmetric difference of array1 and array2 and write the + * result + * to arrayout. + * It is assumed that arrayout is distinct from both array1 and array2. + */ +void array_container_xor(const array_container_t *array_1, + const array_container_t *array_2, + array_container_t *out) { + const int32_t card_1 = array_1->cardinality, card_2 = array_2->cardinality; + const int32_t max_cardinality = card_1 + card_2; + if (out->capacity < max_cardinality) { + array_container_grow(out, max_cardinality, false); + } + +#ifdef ROARING_VECTOR_OPERATIONS_ENABLED + out->cardinality = + xor_vector16(array_1->array, array_1->cardinality, array_2->array, + array_2->cardinality, out->array); +#else + out->cardinality = + xor_uint16(array_1->array, array_1->cardinality, array_2->array, + array_2->cardinality, out->array); +#endif +} + +static inline int32_t minimum_int32(int32_t a, int32_t b) { + return (a < b) ? a : b; +} + +/* computes the intersection of array1 and array2 and write the result to + * arrayout. + * It is assumed that arrayout is distinct from both array1 and array2. + * */ +void array_container_intersection(const array_container_t *array1, + const array_container_t *array2, + array_container_t *out) { + int32_t card_1 = array1->cardinality, card_2 = array2->cardinality, + min_card = minimum_int32(card_1, card_2); + const int threshold = 64; // subject to tuning +#ifdef USEAVX + if (out->capacity < min_card) { + array_container_grow(out, min_card + sizeof(__m128i) / sizeof(uint16_t), + false); + } +#else + if (out->capacity < min_card) { + array_container_grow(out, min_card, false); + } +#endif + + if (card_1 * threshold < card_2) { + out->cardinality = intersect_skewed_uint16( + array1->array, card_1, array2->array, card_2, out->array); + } else if (card_2 * threshold < card_1) { + out->cardinality = intersect_skewed_uint16( + array2->array, card_2, array1->array, card_1, out->array); + } else { +#ifdef USEAVX + out->cardinality = intersect_vector16( + array1->array, card_1, array2->array, card_2, out->array); +#else + out->cardinality = intersect_uint16(array1->array, card_1, + array2->array, card_2, out->array); +#endif + } +} + +/* computes the size of the intersection of array1 and array2 + * */ +int array_container_intersection_cardinality(const array_container_t *array1, + const array_container_t *array2) { + int32_t card_1 = array1->cardinality, card_2 = array2->cardinality; + const int threshold = 64; // subject to tuning + if (card_1 * threshold < card_2) { + return intersect_skewed_uint16_cardinality(array1->array, card_1, + array2->array, card_2); + } else if (card_2 * threshold < card_1) { + return intersect_skewed_uint16_cardinality(array2->array, card_2, + array1->array, card_1); + } else { +#ifdef USEAVX + return intersect_vector16_cardinality(array1->array, card_1, + array2->array, card_2); +#else + return intersect_uint16_cardinality(array1->array, card_1, + array2->array, card_2); +#endif + } +} + +bool array_container_intersect(const array_container_t *array1, + const array_container_t *array2) { + int32_t card_1 = array1->cardinality, card_2 = array2->cardinality; + const int threshold = 64; // subject to tuning + if (card_1 * threshold < card_2) { + return intersect_skewed_uint16_nonempty( + array1->array, card_1, array2->array, card_2); + } else if (card_2 * threshold < card_1) { + return intersect_skewed_uint16_nonempty( + array2->array, card_2, array1->array, card_1); + } else { + // we do not bother vectorizing + return intersect_uint16_nonempty(array1->array, card_1, + array2->array, card_2); + } +} + +/* computes the intersection of array1 and array2 and write the result to + * array1. + * */ +void array_container_intersection_inplace(array_container_t *src_1, + const array_container_t *src_2) { + // todo: can any of this be vectorized? + int32_t card_1 = src_1->cardinality, card_2 = src_2->cardinality; + const int threshold = 64; // subject to tuning + if (card_1 * threshold < card_2) { + src_1->cardinality = intersect_skewed_uint16( + src_1->array, card_1, src_2->array, card_2, src_1->array); + } else if (card_2 * threshold < card_1) { + src_1->cardinality = intersect_skewed_uint16( + src_2->array, card_2, src_1->array, card_1, src_1->array); + } else { + src_1->cardinality = intersect_uint16( + src_1->array, card_1, src_2->array, card_2, src_1->array); + } +} + +int array_container_to_uint32_array(void *vout, const array_container_t *cont, + uint32_t base) { + int outpos = 0; + uint32_t *out = (uint32_t *)vout; + for (int i = 0; i < cont->cardinality; ++i) { + const uint32_t val = base + cont->array[i]; + memcpy(out + outpos, &val, + sizeof(uint32_t)); // should be compiled as a MOV on x64 + outpos++; + } + return outpos; +} + +void array_container_printf(const array_container_t *v) { + if (v->cardinality == 0) { + printf("{}"); + return; + } + printf("{"); + printf("%d", v->array[0]); + for (int i = 1; i < v->cardinality; ++i) { + printf(",%d", v->array[i]); + } + printf("}"); +} + +void array_container_printf_as_uint32_array(const array_container_t *v, + uint32_t base) { + if (v->cardinality == 0) { + return; + } + printf("%u", v->array[0] + base); + for (int i = 1; i < v->cardinality; ++i) { + printf(",%u", v->array[i] + base); + } +} + +/* Compute the number of runs */ +int32_t array_container_number_of_runs(const array_container_t *a) { + // Can SIMD work here? + int32_t nr_runs = 0; + int32_t prev = -2; + for (const uint16_t *p = a->array; p != a->array + a->cardinality; ++p) { + if (*p != prev + 1) nr_runs++; + prev = *p; + } + return nr_runs; +} + +int32_t array_container_serialize(const array_container_t *container, char *buf) { + int32_t l, off; + uint16_t cardinality = (uint16_t)container->cardinality; + + memcpy(buf, &cardinality, off = sizeof(cardinality)); + l = sizeof(uint16_t) * container->cardinality; + if (l) memcpy(&buf[off], container->array, l); + + return (off + l); +} + +/** + * Writes the underlying array to buf, outputs how many bytes were written. + * The number of bytes written should be + * array_container_size_in_bytes(container). + * + */ +int32_t array_container_write(const array_container_t *container, char *buf) { + memcpy(buf, container->array, container->cardinality * sizeof(uint16_t)); + return array_container_size_in_bytes(container); +} + +bool array_container_is_subset(const array_container_t *container1, + const array_container_t *container2) { + if (container1->cardinality > container2->cardinality) { + return false; + } + int i1 = 0, i2 = 0; + while (i1 < container1->cardinality && i2 < container2->cardinality) { + if (container1->array[i1] == container2->array[i2]) { + i1++; + i2++; + } else if (container1->array[i1] > container2->array[i2]) { + i2++; + } else { // container1->array[i1] < container2->array[i2] + return false; + } + } + if (i1 == container1->cardinality) { + return true; + } else { + return false; + } +} + +int32_t array_container_read(int32_t cardinality, array_container_t *container, + const char *buf) { + if (container->capacity < cardinality) { + array_container_grow(container, cardinality, false); + } + container->cardinality = cardinality; + memcpy(container->array, buf, container->cardinality * sizeof(uint16_t)); + + return array_container_size_in_bytes(container); +} + +uint32_t array_container_serialization_len(const array_container_t *container) { + return (sizeof(uint16_t) /* container->cardinality converted to 16 bit */ + + (sizeof(uint16_t) * container->cardinality)); +} + +void *array_container_deserialize(const char *buf, size_t buf_len) { + array_container_t *ptr; + + if (buf_len < 2) /* capacity converted to 16 bit */ + return (NULL); + else + buf_len -= 2; + + if ((ptr = (array_container_t *)malloc(sizeof(array_container_t))) != + NULL) { + size_t len; + int32_t off; + uint16_t cardinality; + + memcpy(&cardinality, buf, off = sizeof(cardinality)); + + ptr->capacity = ptr->cardinality = (uint32_t)cardinality; + len = sizeof(uint16_t) * ptr->cardinality; + + if (len != buf_len) { + free(ptr); + return (NULL); + } + + if ((ptr->array = (uint16_t *)malloc(sizeof(uint16_t) * + ptr->capacity)) == NULL) { + free(ptr); + return (NULL); + } + + if (len) memcpy(ptr->array, &buf[off], len); + + /* Check if returned values are monotonically increasing */ + for (int32_t i = 0, j = 0; i < ptr->cardinality; i++) { + if (ptr->array[i] < j) { + free(ptr->array); + free(ptr); + return (NULL); + } else + j = ptr->array[i]; + } + } + + return (ptr); +} + +bool array_container_iterate(const array_container_t *cont, uint32_t base, + roaring_iterator iterator, void *ptr) { + for (int i = 0; i < cont->cardinality; i++) + if (!iterator(cont->array[i] + base, ptr)) return false; + return true; +} + +bool array_container_iterate64(const array_container_t *cont, uint32_t base, + roaring_iterator64 iterator, uint64_t high_bits, + void *ptr) { + for (int i = 0; i < cont->cardinality; i++) + if (!iterator(high_bits | (uint64_t)(cont->array[i] + base), ptr)) + return false; + return true; +} +/* end file src/containers/array.c */ +/* begin file src/containers/bitset.c */ +/* + * bitset.c + * + */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L +#endif +#include +#include +#include +#include + + +void bitset_container_clear(bitset_container_t *bitset) { + memset(bitset->array, 0, sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); + bitset->cardinality = 0; +} + +void bitset_container_set_all(bitset_container_t *bitset) { + memset(bitset->array, INT64_C(-1), + sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); + bitset->cardinality = (1 << 16); +} + + + +/* Create a new bitset. Return NULL in case of failure. */ +bitset_container_t *bitset_container_create(void) { + bitset_container_t *bitset = + (bitset_container_t *)malloc(sizeof(bitset_container_t)); + + if (!bitset) { + return NULL; + } + // sizeof(__m256i) == 32 + bitset->array = (uint64_t *)roaring_bitmap_aligned_malloc( + 32, sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); + if (!bitset->array) { + free(bitset); + return NULL; + } + bitset_container_clear(bitset); + return bitset; +} + +/* Copy one container into another. We assume that they are distinct. */ +void bitset_container_copy(const bitset_container_t *source, + bitset_container_t *dest) { + dest->cardinality = source->cardinality; + memcpy(dest->array, source->array, + sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); +} + +void bitset_container_add_from_range(bitset_container_t *bitset, uint32_t min, + uint32_t max, uint16_t step) { + if (step == 0) return; // refuse to crash + if ((64 % step) == 0) { // step divides 64 + uint64_t mask = 0; // construct the repeated mask + for (uint32_t value = (min % step); value < 64; value += step) { + mask |= ((uint64_t)1 << value); + } + uint32_t firstword = min / 64; + uint32_t endword = (max - 1) / 64; + bitset->cardinality = (max - min + step - 1) / step; + if (firstword == endword) { + bitset->array[firstword] |= + mask & (((~UINT64_C(0)) << (min % 64)) & + ((~UINT64_C(0)) >> ((~max + 1) % 64))); + return; + } + bitset->array[firstword] = mask & ((~UINT64_C(0)) << (min % 64)); + for (uint32_t i = firstword + 1; i < endword; i++) + bitset->array[i] = mask; + bitset->array[endword] = mask & ((~UINT64_C(0)) >> ((~max + 1) % 64)); + } else { + for (uint32_t value = min; value < max; value += step) { + bitset_container_add(bitset, value); + } + } +} + +/* Free memory. */ +void bitset_container_free(bitset_container_t *bitset) { + if(bitset->array != NULL) {// Jon Strabala reports that some tools complain otherwise + roaring_bitmap_aligned_free(bitset->array); + bitset->array = NULL; // pedantic + } + free(bitset); +} + +/* duplicate container. */ +bitset_container_t *bitset_container_clone(const bitset_container_t *src) { + bitset_container_t *bitset = + (bitset_container_t *)malloc(sizeof(bitset_container_t)); + assert(bitset); + + // sizeof(__m256i) == 32 + bitset->array = (uint64_t *)roaring_bitmap_aligned_malloc( + 32, sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); + assert(bitset->array); + bitset->cardinality = src->cardinality; + memcpy(bitset->array, src->array, + sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); + return bitset; +} + +void bitset_container_set_range(bitset_container_t *bitset, uint32_t begin, + uint32_t end) { + bitset_set_range(bitset->array, begin, end); + bitset->cardinality = + bitset_container_compute_cardinality(bitset); // could be smarter +} + + +bool bitset_container_intersect(const bitset_container_t *src_1, + const bitset_container_t *src_2) { + // could vectorize, but this is probably already quite fast in practice + const uint64_t * __restrict__ array_1 = src_1->array; + const uint64_t * __restrict__ array_2 = src_2->array; + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i ++) { + if((array_1[i] & array_2[i]) != 0) return true; + } + return false; +} + + +#ifdef USEAVX +#ifndef WORDS_IN_AVX2_REG +#define WORDS_IN_AVX2_REG sizeof(__m256i) / sizeof(uint64_t) +#endif +/* Get the number of bits set (force computation) */ +int bitset_container_compute_cardinality(const bitset_container_t *bitset) { + return (int) avx2_harley_seal_popcount256( + (const __m256i *)bitset->array, + BITSET_CONTAINER_SIZE_IN_WORDS / (WORDS_IN_AVX2_REG)); +} + +#elif defined(USENEON) +int bitset_container_compute_cardinality(const bitset_container_t *bitset) { + uint16x8_t n0 = vdupq_n_u16(0); + uint16x8_t n1 = vdupq_n_u16(0); + uint16x8_t n2 = vdupq_n_u16(0); + uint16x8_t n3 = vdupq_n_u16(0); + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 8) { + uint64x2_t c0 = vld1q_u64(&bitset->array[i + 0]); + n0 = vaddq_u16(n0, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c0)))); + uint64x2_t c1 = vld1q_u64(&bitset->array[i + 2]); + n1 = vaddq_u16(n1, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c1)))); + uint64x2_t c2 = vld1q_u64(&bitset->array[i + 4]); + n2 = vaddq_u16(n2, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c2)))); + uint64x2_t c3 = vld1q_u64(&bitset->array[i + 6]); + n3 = vaddq_u16(n3, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c3)))); + } + uint64x2_t n = vdupq_n_u64(0); + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n0))); + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n1))); + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n2))); + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n3))); + return vgetq_lane_u64(n, 0) + vgetq_lane_u64(n, 1); +} + +#else + +/* Get the number of bits set (force computation) */ +int bitset_container_compute_cardinality(const bitset_container_t *bitset) { + const uint64_t *array = bitset->array; + int32_t sum = 0; + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 4) { + sum += hamming(array[i]); + sum += hamming(array[i + 1]); + sum += hamming(array[i + 2]); + sum += hamming(array[i + 3]); + } + return sum; +} + +#endif + +#ifdef USEAVX + +#define BITSET_CONTAINER_FN_REPEAT 8 +#ifndef WORDS_IN_AVX2_REG +#define WORDS_IN_AVX2_REG sizeof(__m256i) / sizeof(uint64_t) +#endif +#define LOOP_SIZE \ + BITSET_CONTAINER_SIZE_IN_WORDS / \ + ((WORDS_IN_AVX2_REG)*BITSET_CONTAINER_FN_REPEAT) + +/* Computes a binary operation (eg union) on bitset1 and bitset2 and write the + result to bitsetout */ +// clang-format off +#define BITSET_CONTAINER_FN(opname, opsymbol, avx_intrinsic, neon_intrinsic) \ +int bitset_container_##opname##_nocard(const bitset_container_t *src_1, \ + const bitset_container_t *src_2, \ + bitset_container_t *dst) { \ + const uint8_t * __restrict__ array_1 = (const uint8_t *)src_1->array; \ + const uint8_t * __restrict__ array_2 = (const uint8_t *)src_2->array; \ + /* not using the blocking optimization for some reason*/ \ + uint8_t *out = (uint8_t*)dst->array; \ + const int innerloop = 8; \ + for (size_t i = 0; \ + i < BITSET_CONTAINER_SIZE_IN_WORDS / (WORDS_IN_AVX2_REG); \ + i+=innerloop) {\ + __m256i A1, A2, AO; \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)out, AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 32)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 32)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+32), AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 64)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 64)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+64), AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 96)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 96)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+96), AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 128)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 128)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+128), AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 160)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 160)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+160), AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 192)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 192)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+192), AO); \ + A1 = _mm256_lddqu_si256((const __m256i *)(array_1 + 224)); \ + A2 = _mm256_lddqu_si256((const __m256i *)(array_2 + 224)); \ + AO = avx_intrinsic(A2, A1); \ + _mm256_storeu_si256((__m256i *)(out+224), AO); \ + out+=256; \ + array_1 += 256; \ + array_2 += 256; \ + } \ + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; \ + return dst->cardinality; \ +} \ +/* next, a version that updates cardinality*/ \ +int bitset_container_##opname(const bitset_container_t *src_1, \ + const bitset_container_t *src_2, \ + bitset_container_t *dst) { \ + const __m256i * __restrict__ array_1 = (const __m256i *) src_1->array; \ + const __m256i * __restrict__ array_2 = (const __m256i *) src_2->array; \ + __m256i *out = (__m256i *) dst->array; \ + dst->cardinality = (int32_t)avx2_harley_seal_popcount256andstore_##opname(array_2,\ + array_1, out,BITSET_CONTAINER_SIZE_IN_WORDS / (WORDS_IN_AVX2_REG));\ + return dst->cardinality; \ +} \ +/* next, a version that just computes the cardinality*/ \ +int bitset_container_##opname##_justcard(const bitset_container_t *src_1, \ + const bitset_container_t *src_2) { \ + const __m256i * __restrict__ data1 = (const __m256i *) src_1->array; \ + const __m256i * __restrict__ data2 = (const __m256i *) src_2->array; \ + return (int)avx2_harley_seal_popcount256_##opname(data2, \ + data1, BITSET_CONTAINER_SIZE_IN_WORDS / (WORDS_IN_AVX2_REG));\ +} + +#elif defined(USENEON) + +#define BITSET_CONTAINER_FN(opname, opsymbol, avx_intrinsic, neon_intrinsic) \ +int bitset_container_##opname(const bitset_container_t *src_1, \ + const bitset_container_t *src_2, \ + bitset_container_t *dst) { \ + const uint64_t * __restrict__ array_1 = src_1->array; \ + const uint64_t * __restrict__ array_2 = src_2->array; \ + uint64_t *out = dst->array; \ + uint16x8_t n0 = vdupq_n_u16(0); \ + uint16x8_t n1 = vdupq_n_u16(0); \ + uint16x8_t n2 = vdupq_n_u16(0); \ + uint16x8_t n3 = vdupq_n_u16(0); \ + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 8) { \ + uint64x2_t c0 = neon_intrinsic(vld1q_u64(&array_1[i + 0]), \ + vld1q_u64(&array_2[i + 0])); \ + n0 = vaddq_u16(n0, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c0)))); \ + vst1q_u64(&out[i + 0], c0); \ + uint64x2_t c1 = neon_intrinsic(vld1q_u64(&array_1[i + 2]), \ + vld1q_u64(&array_2[i + 2])); \ + n1 = vaddq_u16(n1, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c1)))); \ + vst1q_u64(&out[i + 2], c1); \ + uint64x2_t c2 = neon_intrinsic(vld1q_u64(&array_1[i + 4]), \ + vld1q_u64(&array_2[i + 4])); \ + n2 = vaddq_u16(n2, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c2)))); \ + vst1q_u64(&out[i + 4], c2); \ + uint64x2_t c3 = neon_intrinsic(vld1q_u64(&array_1[i + 6]), \ + vld1q_u64(&array_2[i + 6])); \ + n3 = vaddq_u16(n3, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c3)))); \ + vst1q_u64(&out[i + 6], c3); \ + } \ + uint64x2_t n = vdupq_n_u64(0); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n0))); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n1))); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n2))); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n3))); \ + dst->cardinality = vgetq_lane_u64(n, 0) + vgetq_lane_u64(n, 1); \ + return dst->cardinality; \ +} \ +int bitset_container_##opname##_nocard(const bitset_container_t *src_1, \ + const bitset_container_t *src_2, \ + bitset_container_t *dst) { \ + const uint64_t * __restrict__ array_1 = src_1->array; \ + const uint64_t * __restrict__ array_2 = src_2->array; \ + uint64_t *out = dst->array; \ + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 8) { \ + vst1q_u64(&out[i + 0], neon_intrinsic(vld1q_u64(&array_1[i + 0]), \ + vld1q_u64(&array_2[i + 0]))); \ + vst1q_u64(&out[i + 2], neon_intrinsic(vld1q_u64(&array_1[i + 2]), \ + vld1q_u64(&array_2[i + 2]))); \ + vst1q_u64(&out[i + 4], neon_intrinsic(vld1q_u64(&array_1[i + 4]), \ + vld1q_u64(&array_2[i + 4]))); \ + vst1q_u64(&out[i + 6], neon_intrinsic(vld1q_u64(&array_1[i + 6]), \ + vld1q_u64(&array_2[i + 6]))); \ + } \ + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; \ + return dst->cardinality; \ +} \ +int bitset_container_##opname##_justcard(const bitset_container_t *src_1, \ + const bitset_container_t *src_2) { \ + const uint64_t * __restrict__ array_1 = src_1->array; \ + const uint64_t * __restrict__ array_2 = src_2->array; \ + uint16x8_t n0 = vdupq_n_u16(0); \ + uint16x8_t n1 = vdupq_n_u16(0); \ + uint16x8_t n2 = vdupq_n_u16(0); \ + uint16x8_t n3 = vdupq_n_u16(0); \ + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 8) { \ + uint64x2_t c0 = neon_intrinsic(vld1q_u64(&array_1[i + 0]), \ + vld1q_u64(&array_2[i + 0])); \ + n0 = vaddq_u16(n0, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c0)))); \ + uint64x2_t c1 = neon_intrinsic(vld1q_u64(&array_1[i + 2]), \ + vld1q_u64(&array_2[i + 2])); \ + n1 = vaddq_u16(n1, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c1)))); \ + uint64x2_t c2 = neon_intrinsic(vld1q_u64(&array_1[i + 4]), \ + vld1q_u64(&array_2[i + 4])); \ + n2 = vaddq_u16(n2, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c2)))); \ + uint64x2_t c3 = neon_intrinsic(vld1q_u64(&array_1[i + 6]), \ + vld1q_u64(&array_2[i + 6])); \ + n3 = vaddq_u16(n3, vpaddlq_u8(vcntq_u8(vreinterpretq_u8_u64(c3)))); \ + } \ + uint64x2_t n = vdupq_n_u64(0); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n0))); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n1))); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n2))); \ + n = vaddq_u64(n, vpaddlq_u32(vpaddlq_u16(n3))); \ + return vgetq_lane_u64(n, 0) + vgetq_lane_u64(n, 1); \ +} + +#else /* not USEAVX */ + +#define BITSET_CONTAINER_FN(opname, opsymbol, avx_intrinsic, neon_intrinsic) \ +int bitset_container_##opname(const bitset_container_t *src_1, \ + const bitset_container_t *src_2, \ + bitset_container_t *dst) { \ + const uint64_t * __restrict__ array_1 = src_1->array; \ + const uint64_t * __restrict__ array_2 = src_2->array; \ + uint64_t *out = dst->array; \ + int32_t sum = 0; \ + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 2) { \ + const uint64_t word_1 = (array_1[i])opsymbol(array_2[i]), \ + word_2 = (array_1[i + 1])opsymbol(array_2[i + 1]); \ + out[i] = word_1; \ + out[i + 1] = word_2; \ + sum += hamming(word_1); \ + sum += hamming(word_2); \ + } \ + dst->cardinality = sum; \ + return dst->cardinality; \ +} \ +int bitset_container_##opname##_nocard(const bitset_container_t *src_1, \ + const bitset_container_t *src_2, \ + bitset_container_t *dst) { \ + const uint64_t * __restrict__ array_1 = src_1->array; \ + const uint64_t * __restrict__ array_2 = src_2->array; \ + uint64_t *out = dst->array; \ + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i++) { \ + out[i] = (array_1[i])opsymbol(array_2[i]); \ + } \ + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; \ + return dst->cardinality; \ +} \ +int bitset_container_##opname##_justcard(const bitset_container_t *src_1, \ + const bitset_container_t *src_2) { \ + const uint64_t * __restrict__ array_1 = src_1->array; \ + const uint64_t * __restrict__ array_2 = src_2->array; \ + int32_t sum = 0; \ + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 2) { \ + const uint64_t word_1 = (array_1[i])opsymbol(array_2[i]), \ + word_2 = (array_1[i + 1])opsymbol(array_2[i + 1]); \ + sum += hamming(word_1); \ + sum += hamming(word_2); \ + } \ + return sum; \ +} + +#endif + +// we duplicate the function because other containers use the "or" term, makes API more consistent +BITSET_CONTAINER_FN(or, |, _mm256_or_si256, vorrq_u64) +BITSET_CONTAINER_FN(union, |, _mm256_or_si256, vorrq_u64) + +// we duplicate the function because other containers use the "intersection" term, makes API more consistent +BITSET_CONTAINER_FN(and, &, _mm256_and_si256, vandq_u64) +BITSET_CONTAINER_FN(intersection, &, _mm256_and_si256, vandq_u64) + +BITSET_CONTAINER_FN(xor, ^, _mm256_xor_si256, veorq_u64) +BITSET_CONTAINER_FN(andnot, &~, _mm256_andnot_si256, vbicq_u64) +// clang-format On + + + +int bitset_container_to_uint32_array( void *vout, const bitset_container_t *cont, uint32_t base) { +#ifdef USEAVX2FORDECODING + if(cont->cardinality >= 8192)// heuristic + return (int) bitset_extract_setbits_avx2(cont->array, BITSET_CONTAINER_SIZE_IN_WORDS, vout,cont->cardinality,base); + else + return (int) bitset_extract_setbits(cont->array, BITSET_CONTAINER_SIZE_IN_WORDS, vout,base); +#else + return (int) bitset_extract_setbits(cont->array, BITSET_CONTAINER_SIZE_IN_WORDS, vout,base); +#endif +} + +/* + * Print this container using printf (useful for debugging). + */ +void bitset_container_printf(const bitset_container_t * v) { + printf("{"); + uint32_t base = 0; + bool iamfirst = true;// TODO: rework so that this is not necessary yet still readable + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i) { + uint64_t w = v->array[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + if(iamfirst) {// predicted to be false + printf("%u",base + r); + iamfirst = false; + } else { + printf(",%u",base + r); + } + w ^= t; + } + base += 64; + } + printf("}"); +} + + +/* + * Print this container using printf as a comma-separated list of 32-bit integers starting at base. + */ +void bitset_container_printf_as_uint32_array(const bitset_container_t * v, uint32_t base) { + bool iamfirst = true;// TODO: rework so that this is not necessary yet still readable + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i) { + uint64_t w = v->array[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + if(iamfirst) {// predicted to be false + printf("%u", r + base); + iamfirst = false; + } else { + printf(",%u",r + base); + } + w ^= t; + } + base += 64; + } +} + + +// TODO: use the fast lower bound, also +int bitset_container_number_of_runs(bitset_container_t *b) { + int num_runs = 0; + uint64_t next_word = b->array[0]; + + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS-1; ++i) { + uint64_t word = next_word; + next_word = b->array[i+1]; + num_runs += hamming((~word) & (word << 1)) + ( (word >> 63) & ~next_word); + } + + uint64_t word = next_word; + num_runs += hamming((~word) & (word << 1)); + if((word & 0x8000000000000000ULL) != 0) + num_runs++; + return num_runs; +} + +int32_t bitset_container_serialize(const bitset_container_t *container, char *buf) { + int32_t l = sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS; + memcpy(buf, container->array, l); + return(l); +} + + + +int32_t bitset_container_write(const bitset_container_t *container, + char *buf) { + memcpy(buf, container->array, BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t)); + return bitset_container_size_in_bytes(container); +} + + +int32_t bitset_container_read(int32_t cardinality, bitset_container_t *container, + const char *buf) { + container->cardinality = cardinality; + memcpy(container->array, buf, BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t)); + return bitset_container_size_in_bytes(container); +} + +uint32_t bitset_container_serialization_len(void) { + return(sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS); +} + +void* bitset_container_deserialize(const char *buf, size_t buf_len) { + bitset_container_t *ptr; + size_t l = sizeof(uint64_t) * BITSET_CONTAINER_SIZE_IN_WORDS; + + if(l != buf_len) + return(NULL); + + if((ptr = (bitset_container_t *)malloc(sizeof(bitset_container_t))) != NULL) { + memcpy(ptr, buf, sizeof(bitset_container_t)); + // sizeof(__m256i) == 32 + ptr->array = (uint64_t *) roaring_bitmap_aligned_malloc(32, l); + if (! ptr->array) { + free(ptr); + return NULL; + } + memcpy(ptr->array, buf, l); + ptr->cardinality = bitset_container_compute_cardinality(ptr); + } + + return((void*)ptr); +} + +bool bitset_container_iterate(const bitset_container_t *cont, uint32_t base, roaring_iterator iterator, void *ptr) { + for (int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i ) { + uint64_t w = cont->array[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + if(!iterator(r + base, ptr)) return false; + w ^= t; + } + base += 64; + } + return true; +} + +bool bitset_container_iterate64(const bitset_container_t *cont, uint32_t base, roaring_iterator64 iterator, uint64_t high_bits, void *ptr) { + for (int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i ) { + uint64_t w = cont->array[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + if(!iterator(high_bits | (uint64_t)(r + base), ptr)) return false; + w ^= t; + } + base += 64; + } + return true; +} + + +bool bitset_container_equals(const bitset_container_t *container1, const bitset_container_t *container2) { + if((container1->cardinality != BITSET_UNKNOWN_CARDINALITY) && (container2->cardinality != BITSET_UNKNOWN_CARDINALITY)) { + if(container1->cardinality != container2->cardinality) { + return false; + } + if (container1->cardinality == INT32_C(0x10000)) { + return true; + } + } +#ifdef USEAVX + const __m256i *ptr1 = (const __m256i*)container1->array; + const __m256i *ptr2 = (const __m256i*)container2->array; + for (size_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS*sizeof(uint64_t)/32; i++) { + __m256i r1 = _mm256_load_si256(ptr1+i); + __m256i r2 = _mm256_load_si256(ptr2+i); + int mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(r1, r2)); + if ((uint32_t)mask != UINT32_MAX) { + return false; + } + } +#else + return memcmp(container1->array, + container2->array, + BITSET_CONTAINER_SIZE_IN_WORDS*sizeof(uint64_t)) == 0; +#endif + return true; +} + +bool bitset_container_is_subset(const bitset_container_t *container1, + const bitset_container_t *container2) { + if((container1->cardinality != BITSET_UNKNOWN_CARDINALITY) && (container2->cardinality != BITSET_UNKNOWN_CARDINALITY)) { + if(container1->cardinality > container2->cardinality) { + return false; + } + } + for(int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i ) { + if((container1->array[i] & container2->array[i]) != container1->array[i]) { + return false; + } + } + return true; +} + +bool bitset_container_select(const bitset_container_t *container, uint32_t *start_rank, uint32_t rank, uint32_t *element) { + int card = bitset_container_cardinality(container); + if(rank >= *start_rank + card) { + *start_rank += card; + return false; + } + const uint64_t *array = container->array; + int32_t size; + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i += 1) { + size = hamming(array[i]); + if(rank <= *start_rank + size) { + uint64_t w = container->array[i]; + uint16_t base = i*64; + while (w != 0) { + uint64_t t = w & (~w + 1); + int r = __builtin_ctzll(w); + if(*start_rank == rank) { + *element = r+base; + return true; + } + w ^= t; + *start_rank += 1; + } + } + else + *start_rank += size; + } + assert(false); + __builtin_unreachable(); +} + + +/* Returns the smallest value (assumes not empty) */ +uint16_t bitset_container_minimum(const bitset_container_t *container) { + for (int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i ) { + uint64_t w = container->array[i]; + if (w != 0) { + int r = __builtin_ctzll(w); + return r + i * 64; + } + } + return UINT16_MAX; +} + +/* Returns the largest value (assumes not empty) */ +uint16_t bitset_container_maximum(const bitset_container_t *container) { + for (int32_t i = BITSET_CONTAINER_SIZE_IN_WORDS - 1; i > 0; --i ) { + uint64_t w = container->array[i]; + if (w != 0) { + int r = __builtin_clzll(w); + return i * 64 + 63 - r; + } + } + return 0; +} + +/* Returns the number of values equal or smaller than x */ +int bitset_container_rank(const bitset_container_t *container, uint16_t x) { + // credit: aqrit + int sum = 0; + int i = 0; + for (int end = x / 64; i < end; i++){ + sum += hamming(container->array[i]); + } + uint64_t lastword = container->array[i]; + uint64_t lastpos = UINT64_C(1) << (x % 64); + uint64_t mask = lastpos + lastpos - 1; // smear right + sum += hamming(lastword & mask); + return sum; +} + +/* Returns the index of the first value equal or larger than x, or -1 */ +int bitset_container_index_equalorlarger(const bitset_container_t *container, uint16_t x) { + uint32_t x32 = x; + uint32_t k = x32 / 64; + uint64_t word = container->array[k]; + const int diff = x32 - k * 64; // in [0,64) + word = (word >> diff) << diff; // a mask is faster, but we don't care + while(word == 0) { + k++; + if(k == BITSET_CONTAINER_SIZE_IN_WORDS) return -1; + word = container->array[k]; + } + return k * 64 + __builtin_ctzll(word); +} +/* end file src/containers/bitset.c */ +/* begin file src/containers/containers.c */ + + +void container_free(void *container, uint8_t typecode) { + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + bitset_container_free((bitset_container_t *)container); + break; + case ARRAY_CONTAINER_TYPE_CODE: + array_container_free((array_container_t *)container); + break; + case RUN_CONTAINER_TYPE_CODE: + run_container_free((run_container_t *)container); + break; + case SHARED_CONTAINER_TYPE_CODE: + shared_container_free((shared_container_t *)container); + break; + default: + assert(false); + __builtin_unreachable(); + } +} + +void container_printf(const void *container, uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + bitset_container_printf((const bitset_container_t *)container); + return; + case ARRAY_CONTAINER_TYPE_CODE: + array_container_printf((const array_container_t *)container); + return; + case RUN_CONTAINER_TYPE_CODE: + run_container_printf((const run_container_t *)container); + return; + default: + __builtin_unreachable(); + } +} + +void container_printf_as_uint32_array(const void *container, uint8_t typecode, + uint32_t base) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + bitset_container_printf_as_uint32_array( + (const bitset_container_t *)container, base); + return; + case ARRAY_CONTAINER_TYPE_CODE: + array_container_printf_as_uint32_array( + (const array_container_t *)container, base); + return; + case RUN_CONTAINER_TYPE_CODE: + run_container_printf_as_uint32_array( + (const run_container_t *)container, base); + return; + return; + default: + __builtin_unreachable(); + } +} + +int32_t container_serialize(const void *container, uint8_t typecode, + char *buf) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return (bitset_container_serialize((const bitset_container_t *)container, + buf)); + case ARRAY_CONTAINER_TYPE_CODE: + return ( + array_container_serialize((const array_container_t *)container, buf)); + case RUN_CONTAINER_TYPE_CODE: + return (run_container_serialize((const run_container_t *)container, buf)); + default: + assert(0); + __builtin_unreachable(); + return (-1); + } +} + +uint32_t container_serialization_len(const void *container, uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_serialization_len(); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_serialization_len( + (const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_serialization_len( + (const run_container_t *)container); + default: + assert(0); + __builtin_unreachable(); + return (0); + } +} + +void *container_deserialize(uint8_t typecode, const char *buf, size_t buf_len) { + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return (bitset_container_deserialize(buf, buf_len)); + case ARRAY_CONTAINER_TYPE_CODE: + return (array_container_deserialize(buf, buf_len)); + case RUN_CONTAINER_TYPE_CODE: + return (run_container_deserialize(buf, buf_len)); + case SHARED_CONTAINER_TYPE_CODE: + printf("this should never happen.\n"); + assert(0); + __builtin_unreachable(); + return (NULL); + default: + assert(0); + __builtin_unreachable(); + return (NULL); + } +} + +void *get_copy_of_container(void *container, uint8_t *typecode, + bool copy_on_write) { + if (copy_on_write) { + shared_container_t *shared_container; + if (*typecode == SHARED_CONTAINER_TYPE_CODE) { + shared_container = (shared_container_t *)container; + shared_container->counter += 1; + return shared_container; + } + assert(*typecode != SHARED_CONTAINER_TYPE_CODE); + + if ((shared_container = (shared_container_t *)malloc( + sizeof(shared_container_t))) == NULL) { + return NULL; + } + + shared_container->container = container; + shared_container->typecode = *typecode; + + shared_container->counter = 2; + *typecode = SHARED_CONTAINER_TYPE_CODE; + + return shared_container; + } // copy_on_write + // otherwise, no copy on write... + const void *actualcontainer = + container_unwrap_shared((const void *)container, typecode); + assert(*typecode != SHARED_CONTAINER_TYPE_CODE); + return container_clone(actualcontainer, *typecode); +} +/** + * Copies a container, requires a typecode. This allocates new memory, caller + * is responsible for deallocation. + */ +void *container_clone(const void *container, uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_clone((const bitset_container_t *)container); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_clone((const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_clone((const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + printf("shared containers are not clonable\n"); + assert(false); + return NULL; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +void *shared_container_extract_copy(shared_container_t *container, + uint8_t *typecode) { + assert(container->counter > 0); + assert(container->typecode != SHARED_CONTAINER_TYPE_CODE); + container->counter--; + *typecode = container->typecode; + void *answer; + if (container->counter == 0) { + answer = container->container; + container->container = NULL; // paranoid + free(container); + } else { + answer = container_clone(container->container, *typecode); + } + assert(*typecode != SHARED_CONTAINER_TYPE_CODE); + return answer; +} + +void shared_container_free(shared_container_t *container) { + assert(container->counter > 0); + container->counter--; + if (container->counter == 0) { + assert(container->typecode != SHARED_CONTAINER_TYPE_CODE); + container_free(container->container, container->typecode); + container->container = NULL; // paranoid + free(container); + } +} + +/* end file src/containers/containers.c */ +/* begin file src/containers/convert.c */ +#include + + +// file contains grubby stuff that must know impl. details of all container +// types. +bitset_container_t *bitset_container_from_array(const array_container_t *a) { + bitset_container_t *ans = bitset_container_create(); + int limit = array_container_cardinality(a); + for (int i = 0; i < limit; ++i) bitset_container_set(ans, a->array[i]); + return ans; +} + +bitset_container_t *bitset_container_from_run(const run_container_t *arr) { + int card = run_container_cardinality(arr); + bitset_container_t *answer = bitset_container_create(); + for (int rlepos = 0; rlepos < arr->n_runs; ++rlepos) { + rle16_t vl = arr->runs[rlepos]; + bitset_set_lenrange(answer->array, vl.value, vl.length); + } + answer->cardinality = card; + return answer; +} + +array_container_t *array_container_from_run(const run_container_t *arr) { + array_container_t *answer = + array_container_create_given_capacity(run_container_cardinality(arr)); + answer->cardinality = 0; + for (int rlepos = 0; rlepos < arr->n_runs; ++rlepos) { + int run_start = arr->runs[rlepos].value; + int run_end = run_start + arr->runs[rlepos].length; + + for (int run_value = run_start; run_value <= run_end; ++run_value) { + answer->array[answer->cardinality++] = (uint16_t)run_value; + } + } + return answer; +} + +array_container_t *array_container_from_bitset(const bitset_container_t *bits) { + array_container_t *result = + array_container_create_given_capacity(bits->cardinality); + result->cardinality = bits->cardinality; + // sse version ends up being slower here + // (bitset_extract_setbits_sse_uint16) + // because of the sparsity of the data + bitset_extract_setbits_uint16(bits->array, BITSET_CONTAINER_SIZE_IN_WORDS, + result->array, 0); + return result; +} + +/* assumes that container has adequate space. Run from [s,e] (inclusive) */ +static void add_run(run_container_t *r, int s, int e) { + r->runs[r->n_runs].value = s; + r->runs[r->n_runs].length = e - s; + r->n_runs++; +} + +run_container_t *run_container_from_array(const array_container_t *c) { + int32_t n_runs = array_container_number_of_runs(c); + run_container_t *answer = run_container_create_given_capacity(n_runs); + int prev = -2; + int run_start = -1; + int32_t card = c->cardinality; + if (card == 0) return answer; + for (int i = 0; i < card; ++i) { + const uint16_t cur_val = c->array[i]; + if (cur_val != prev + 1) { + // new run starts; flush old one, if any + if (run_start != -1) add_run(answer, run_start, prev); + run_start = cur_val; + } + prev = c->array[i]; + } + // now prev is the last seen value + add_run(answer, run_start, prev); + // assert(run_container_cardinality(answer) == c->cardinality); + return answer; +} + +/** + * Convert the runcontainer to either a Bitmap or an Array Container, depending + * on the cardinality. Frees the container. + * Allocates and returns new container, which caller is responsible for freeing. + * It does not free the run container. + */ + +void *convert_to_bitset_or_array_container(run_container_t *r, int32_t card, + uint8_t *resulttype) { + if (card <= DEFAULT_MAX_SIZE) { + array_container_t *answer = array_container_create_given_capacity(card); + answer->cardinality = 0; + for (int rlepos = 0; rlepos < r->n_runs; ++rlepos) { + uint16_t run_start = r->runs[rlepos].value; + uint16_t run_end = run_start + r->runs[rlepos].length; + for (uint16_t run_value = run_start; run_value <= run_end; + ++run_value) { + answer->array[answer->cardinality++] = run_value; + } + } + assert(card == answer->cardinality); + *resulttype = ARRAY_CONTAINER_TYPE_CODE; + //run_container_free(r); + return answer; + } + bitset_container_t *answer = bitset_container_create(); + for (int rlepos = 0; rlepos < r->n_runs; ++rlepos) { + uint16_t run_start = r->runs[rlepos].value; + bitset_set_lenrange(answer->array, run_start, r->runs[rlepos].length); + } + answer->cardinality = card; + *resulttype = BITSET_CONTAINER_TYPE_CODE; + //run_container_free(r); + return answer; +} + +/* Converts a run container to either an array or a bitset, IF it saves space. + */ +/* If a conversion occurs, the caller is responsible to free the original + * container and + * he becomes responsible to free the new one. */ +void *convert_run_to_efficient_container(run_container_t *c, + uint8_t *typecode_after) { + int32_t size_as_run_container = + run_container_serialized_size_in_bytes(c->n_runs); + + int32_t size_as_bitset_container = + bitset_container_serialized_size_in_bytes(); + int32_t card = run_container_cardinality(c); + int32_t size_as_array_container = + array_container_serialized_size_in_bytes(card); + + int32_t min_size_non_run = + size_as_bitset_container < size_as_array_container + ? size_as_bitset_container + : size_as_array_container; + if (size_as_run_container <= min_size_non_run) { // no conversion + *typecode_after = RUN_CONTAINER_TYPE_CODE; + return c; + } + if (card <= DEFAULT_MAX_SIZE) { + // to array + array_container_t *answer = array_container_create_given_capacity(card); + answer->cardinality = 0; + for (int rlepos = 0; rlepos < c->n_runs; ++rlepos) { + int run_start = c->runs[rlepos].value; + int run_end = run_start + c->runs[rlepos].length; + + for (int run_value = run_start; run_value <= run_end; ++run_value) { + answer->array[answer->cardinality++] = (uint16_t)run_value; + } + } + *typecode_after = ARRAY_CONTAINER_TYPE_CODE; + return answer; + } + + // else to bitset + bitset_container_t *answer = bitset_container_create(); + + for (int rlepos = 0; rlepos < c->n_runs; ++rlepos) { + int start = c->runs[rlepos].value; + int end = start + c->runs[rlepos].length; + bitset_set_range(answer->array, start, end + 1); + } + answer->cardinality = card; + *typecode_after = BITSET_CONTAINER_TYPE_CODE; + return answer; +} + +// like convert_run_to_efficient_container but frees the old result if needed +void *convert_run_to_efficient_container_and_free(run_container_t *c, + uint8_t *typecode_after) { + void *answer = convert_run_to_efficient_container(c, typecode_after); + if (answer != c) run_container_free(c); + return answer; +} + +/* once converted, the original container is disposed here, rather than + in roaring_array +*/ + +// TODO: split into run- array- and bitset- subfunctions for sanity; +// a few function calls won't really matter. + +void *convert_run_optimize(void *c, uint8_t typecode_original, + uint8_t *typecode_after) { + if (typecode_original == RUN_CONTAINER_TYPE_CODE) { + void *newc = convert_run_to_efficient_container((run_container_t *)c, + typecode_after); + if (newc != c) { + container_free(c, typecode_original); + } + return newc; + } else if (typecode_original == ARRAY_CONTAINER_TYPE_CODE) { + // it might need to be converted to a run container. + array_container_t *c_qua_array = (array_container_t *)c; + int32_t n_runs = array_container_number_of_runs(c_qua_array); + int32_t size_as_run_container = + run_container_serialized_size_in_bytes(n_runs); + int32_t card = array_container_cardinality(c_qua_array); + int32_t size_as_array_container = + array_container_serialized_size_in_bytes(card); + + if (size_as_run_container >= size_as_array_container) { + *typecode_after = ARRAY_CONTAINER_TYPE_CODE; + return c; + } + // else convert array to run container + run_container_t *answer = run_container_create_given_capacity(n_runs); + int prev = -2; + int run_start = -1; + + assert(card > 0); + for (int i = 0; i < card; ++i) { + uint16_t cur_val = c_qua_array->array[i]; + if (cur_val != prev + 1) { + // new run starts; flush old one, if any + if (run_start != -1) add_run(answer, run_start, prev); + run_start = cur_val; + } + prev = c_qua_array->array[i]; + } + assert(run_start >= 0); + // now prev is the last seen value + add_run(answer, run_start, prev); + *typecode_after = RUN_CONTAINER_TYPE_CODE; + array_container_free(c_qua_array); + return answer; + } else if (typecode_original == + BITSET_CONTAINER_TYPE_CODE) { // run conversions on bitset + // does bitset need conversion to run? + bitset_container_t *c_qua_bitset = (bitset_container_t *)c; + int32_t n_runs = bitset_container_number_of_runs(c_qua_bitset); + int32_t size_as_run_container = + run_container_serialized_size_in_bytes(n_runs); + int32_t size_as_bitset_container = + bitset_container_serialized_size_in_bytes(); + + if (size_as_bitset_container <= size_as_run_container) { + // no conversion needed. + *typecode_after = BITSET_CONTAINER_TYPE_CODE; + return c; + } + // bitset to runcontainer (ported from Java RunContainer( + // BitmapContainer bc, int nbrRuns)) + assert(n_runs > 0); // no empty bitmaps + run_container_t *answer = run_container_create_given_capacity(n_runs); + + int long_ctr = 0; + uint64_t cur_word = c_qua_bitset->array[0]; + int run_count = 0; + while (true) { + while (cur_word == UINT64_C(0) && + long_ctr < BITSET_CONTAINER_SIZE_IN_WORDS - 1) + cur_word = c_qua_bitset->array[++long_ctr]; + + if (cur_word == UINT64_C(0)) { + bitset_container_free(c_qua_bitset); + *typecode_after = RUN_CONTAINER_TYPE_CODE; + return answer; + } + + int local_run_start = __builtin_ctzll(cur_word); + int run_start = local_run_start + 64 * long_ctr; + uint64_t cur_word_with_1s = cur_word | (cur_word - 1); + + int run_end = 0; + while (cur_word_with_1s == UINT64_C(0xFFFFFFFFFFFFFFFF) && + long_ctr < BITSET_CONTAINER_SIZE_IN_WORDS - 1) + cur_word_with_1s = c_qua_bitset->array[++long_ctr]; + + if (cur_word_with_1s == UINT64_C(0xFFFFFFFFFFFFFFFF)) { + run_end = 64 + long_ctr * 64; // exclusive, I guess + add_run(answer, run_start, run_end - 1); + bitset_container_free(c_qua_bitset); + *typecode_after = RUN_CONTAINER_TYPE_CODE; + return answer; + } + int local_run_end = __builtin_ctzll(~cur_word_with_1s); + run_end = local_run_end + long_ctr * 64; + add_run(answer, run_start, run_end - 1); + run_count++; + cur_word = cur_word_with_1s & (cur_word_with_1s + 1); + } + return answer; + } else { + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +bitset_container_t *bitset_container_from_run_range(const run_container_t *run, + uint32_t min, uint32_t max) { + bitset_container_t *bitset = bitset_container_create(); + int32_t union_cardinality = 0; + for (int32_t i = 0; i < run->n_runs; ++i) { + uint32_t rle_min = run->runs[i].value; + uint32_t rle_max = rle_min + run->runs[i].length; + bitset_set_lenrange(bitset->array, rle_min, rle_max - rle_min); + union_cardinality += run->runs[i].length + 1; + } + union_cardinality += max - min + 1; + union_cardinality -= bitset_lenrange_cardinality(bitset->array, min, max-min); + bitset_set_lenrange(bitset->array, min, max - min); + bitset->cardinality = union_cardinality; + return bitset; +} +/* end file src/containers/convert.c */ +/* begin file src/containers/mixed_andnot.c */ +/* + * mixed_andnot.c. More methods since operation is not symmetric, + * except no "wide" andnot , so no lazy options motivated. + */ + +#include +#include + + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst, a valid array container that could be the same as dst.*/ +void array_bitset_container_andnot(const array_container_t *src_1, + const bitset_container_t *src_2, + array_container_t *dst) { + // follows Java implementation as of June 2016 + if (dst->capacity < src_1->cardinality) { + array_container_grow(dst, src_1->cardinality, false); + } + int32_t newcard = 0; + const int32_t origcard = src_1->cardinality; + for (int i = 0; i < origcard; ++i) { + uint16_t key = src_1->array[i]; + dst->array[newcard] = key; + newcard += 1 - bitset_container_contains(src_2, key); + } + dst->cardinality = newcard; +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * src_1 */ + +void array_bitset_container_iandnot(array_container_t *src_1, + const bitset_container_t *src_2) { + array_bitset_container_andnot(src_1, src_2, src_1); +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst, which does not initially have a valid container. + * Return true for a bitset result; false for array + */ + +bool bitset_array_container_andnot(const bitset_container_t *src_1, + const array_container_t *src_2, void **dst) { + // Java did this directly, but we have option of asm or avx + bitset_container_t *result = bitset_container_create(); + bitset_container_copy(src_1, result); + result->cardinality = + (int32_t)bitset_clear_list(result->array, (uint64_t)result->cardinality, + src_2->array, (uint64_t)src_2->cardinality); + + // do required type conversions. + if (result->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(result); + bitset_container_free(result); + return false; + } + *dst = result; + return true; +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_array_container_iandnot(bitset_container_t *src_1, + const array_container_t *src_2, + void **dst) { + *dst = src_1; + src_1->cardinality = + (int32_t)bitset_clear_list(src_1->array, (uint64_t)src_1->cardinality, + src_2->array, (uint64_t)src_2->cardinality); + + if (src_1->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(src_1); + bitset_container_free(src_1); + return false; // not bitset + } else + return true; +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_andnot(const run_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + // follows the Java implementation as of June 2016 + int card = run_container_cardinality(src_1); + if (card <= DEFAULT_MAX_SIZE) { + // must be an array + array_container_t *answer = array_container_create_given_capacity(card); + answer->cardinality = 0; + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + for (int run_value = rle.value; run_value <= rle.value + rle.length; + ++run_value) { + if (!bitset_container_get(src_2, (uint16_t)run_value)) { + answer->array[answer->cardinality++] = (uint16_t)run_value; + } + } + } + *dst = answer; + return false; + } else { // we guess it will be a bitset, though have to check guess when + // done + bitset_container_t *answer = bitset_container_clone(src_2); + + uint32_t last_pos = 0; + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + + uint32_t start = rle.value; + uint32_t end = start + rle.length + 1; + bitset_reset_range(answer->array, last_pos, start); + bitset_flip_range(answer->array, start, end); + last_pos = end; + } + bitset_reset_range(answer->array, last_pos, (uint32_t)(1 << 16)); + + answer->cardinality = bitset_container_compute_cardinality(answer); + + if (answer->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(answer); + bitset_container_free(answer); + return false; // not bitset + } + *dst = answer; + return true; // bitset + } +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_iandnot(run_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + // dummy implementation + bool ans = run_bitset_container_andnot(src_1, src_2, dst); + run_container_free(src_1); + return ans; +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool bitset_run_container_andnot(const bitset_container_t *src_1, + const run_container_t *src_2, void **dst) { + // follows Java implementation + bitset_container_t *result = bitset_container_create(); + + bitset_container_copy(src_1, result); + for (int32_t rlepos = 0; rlepos < src_2->n_runs; ++rlepos) { + rle16_t rle = src_2->runs[rlepos]; + bitset_reset_range(result->array, rle.value, + rle.value + rle.length + UINT32_C(1)); + } + result->cardinality = bitset_container_compute_cardinality(result); + + if (result->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(result); + bitset_container_free(result); + return false; // not bitset + } + *dst = result; + return true; // bitset +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_run_container_iandnot(bitset_container_t *src_1, + const run_container_t *src_2, void **dst) { + *dst = src_1; + + for (int32_t rlepos = 0; rlepos < src_2->n_runs; ++rlepos) { + rle16_t rle = src_2->runs[rlepos]; + bitset_reset_range(src_1->array, rle.value, + rle.value + rle.length + UINT32_C(1)); + } + src_1->cardinality = bitset_container_compute_cardinality(src_1); + + if (src_1->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(src_1); + bitset_container_free(src_1); + return false; // not bitset + } else + return true; +} + +/* helper. a_out must be a valid array container with adequate capacity. + * Returns the cardinality of the output container. Partly Based on Java + * implementation Util.unsignedDifference. + * + * TODO: Util.unsignedDifference does not use advanceUntil. Is it cheaper + * to avoid advanceUntil? + */ + +static int run_array_array_subtract(const run_container_t *r, + const array_container_t *a_in, + array_container_t *a_out) { + int out_card = 0; + int32_t in_array_pos = + -1; // since advanceUntil always assumes we start the search AFTER this + + for (int rlepos = 0; rlepos < r->n_runs; rlepos++) { + int32_t start = r->runs[rlepos].value; + int32_t end = start + r->runs[rlepos].length + 1; + + in_array_pos = advanceUntil(a_in->array, in_array_pos, + a_in->cardinality, (uint16_t)start); + + if (in_array_pos >= a_in->cardinality) { // run has no items subtracted + for (int32_t i = start; i < end; ++i) + a_out->array[out_card++] = (uint16_t)i; + } else { + uint16_t next_nonincluded = a_in->array[in_array_pos]; + if (next_nonincluded >= end) { + // another case when run goes unaltered + for (int32_t i = start; i < end; ++i) + a_out->array[out_card++] = (uint16_t)i; + in_array_pos--; // ensure we see this item again if necessary + } else { + for (int32_t i = start; i < end; ++i) + if (i != next_nonincluded) + a_out->array[out_card++] = (uint16_t)i; + else // 0 should ensure we don't match + next_nonincluded = + (in_array_pos + 1 >= a_in->cardinality) + ? 0 + : a_in->array[++in_array_pos]; + in_array_pos--; // see again + } + } + } + return out_card; +} + +/* dst does not indicate a valid container initially. Eventually it + * can become any type of container. + */ + +int run_array_container_andnot(const run_container_t *src_1, + const array_container_t *src_2, void **dst) { + // follows the Java impl as of June 2016 + + int card = run_container_cardinality(src_1); + const int arbitrary_threshold = 32; + + if (card <= arbitrary_threshold) { + if (src_2->cardinality == 0) { + *dst = run_container_clone(src_1); + return RUN_CONTAINER_TYPE_CODE; + } + // Java's "lazyandNot.toEfficientContainer" thing + run_container_t *answer = run_container_create_given_capacity( + card + array_container_cardinality(src_2)); + + int rlepos = 0; + int xrlepos = 0; // "x" is src_2 + rle16_t rle = src_1->runs[rlepos]; + int32_t start = rle.value; + int32_t end = start + rle.length + 1; + int32_t xstart = src_2->array[xrlepos]; + + while ((rlepos < src_1->n_runs) && (xrlepos < src_2->cardinality)) { + if (end <= xstart) { + // output the first run + answer->runs[answer->n_runs++] = + (rle16_t){.value = (uint16_t)start, + .length = (uint16_t)(end - start - 1)}; + rlepos++; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + } else if (xstart + 1 <= start) { + // exit the second run + xrlepos++; + if (xrlepos < src_2->cardinality) { + xstart = src_2->array[xrlepos]; + } + } else { + if (start < xstart) { + answer->runs[answer->n_runs++] = + (rle16_t){.value = (uint16_t)start, + .length = (uint16_t)(xstart - start - 1)}; + } + if (xstart + 1 < end) { + start = xstart + 1; + } else { + rlepos++; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + } + } + } + if (rlepos < src_1->n_runs) { + answer->runs[answer->n_runs++] = + (rle16_t){.value = (uint16_t)start, + .length = (uint16_t)(end - start - 1)}; + rlepos++; + if (rlepos < src_1->n_runs) { + memcpy(answer->runs + answer->n_runs, src_1->runs + rlepos, + (src_1->n_runs - rlepos) * sizeof(rle16_t)); + answer->n_runs += (src_1->n_runs - rlepos); + } + } + uint8_t return_type; + *dst = convert_run_to_efficient_container(answer, &return_type); + if (answer != *dst) run_container_free(answer); + return return_type; + } + // else it's a bitmap or array + + if (card <= DEFAULT_MAX_SIZE) { + array_container_t *ac = array_container_create_given_capacity(card); + // nb Java code used a generic iterator-based merge to compute + // difference + ac->cardinality = run_array_array_subtract(src_1, src_2, ac); + *dst = ac; + return ARRAY_CONTAINER_TYPE_CODE; + } + bitset_container_t *ans = bitset_container_from_run(src_1); + bool result_is_bitset = bitset_array_container_iandnot(ans, src_2, dst); + return (result_is_bitset ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE); +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +int run_array_container_iandnot(run_container_t *src_1, + const array_container_t *src_2, void **dst) { + // dummy implementation same as June 2016 Java + int ans = run_array_container_andnot(src_1, src_2, dst); + run_container_free(src_1); + return ans; +} + +/* dst must be a valid array container, allowed to be src_1 */ + +void array_run_container_andnot(const array_container_t *src_1, + const run_container_t *src_2, + array_container_t *dst) { + // basically following Java impl as of June 2016 + if (src_1->cardinality > dst->capacity) { + array_container_grow(dst, src_1->cardinality, false); + } + + if (src_2->n_runs == 0) { + memmove(dst->array, src_1->array, + sizeof(uint16_t) * src_1->cardinality); + dst->cardinality = src_1->cardinality; + return; + } + int32_t run_start = src_2->runs[0].value; + int32_t run_end = run_start + src_2->runs[0].length; + int which_run = 0; + + uint16_t val = 0; + int dest_card = 0; + for (int i = 0; i < src_1->cardinality; ++i) { + val = src_1->array[i]; + if (val < run_start) + dst->array[dest_card++] = val; + else if (val <= run_end) { + ; // omitted item + } else { + do { + if (which_run + 1 < src_2->n_runs) { + ++which_run; + run_start = src_2->runs[which_run].value; + run_end = run_start + src_2->runs[which_run].length; + + } else + run_start = run_end = (1 << 16) + 1; + } while (val > run_end); + --i; + } + } + dst->cardinality = dest_card; +} + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +void array_run_container_iandnot(array_container_t *src_1, + const run_container_t *src_2) { + array_run_container_andnot(src_1, src_2, src_1); +} + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int run_run_container_andnot(const run_container_t *src_1, + const run_container_t *src_2, void **dst) { + run_container_t *ans = run_container_create(); + run_container_andnot(src_1, src_2, ans); + uint8_t typecode_after; + *dst = convert_run_to_efficient_container_and_free(ans, &typecode_after); + return typecode_after; +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +int run_run_container_iandnot(run_container_t *src_1, + const run_container_t *src_2, void **dst) { + // following Java impl as of June 2016 (dummy) + int ans = run_run_container_andnot(src_1, src_2, dst); + run_container_free(src_1); + return ans; +} + +/* + * dst is a valid array container and may be the same as src_1 + */ + +void array_array_container_andnot(const array_container_t *src_1, + const array_container_t *src_2, + array_container_t *dst) { + array_container_andnot(src_1, src_2, dst); +} + +/* inplace array-array andnot will always be able to reuse the space of + * src_1 */ +void array_array_container_iandnot(array_container_t *src_1, + const array_container_t *src_2) { + array_container_andnot(src_1, src_2, src_1); +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). Return value is + * "dst is a bitset" + */ + +bool bitset_bitset_container_andnot(const bitset_container_t *src_1, + const bitset_container_t *src_2, + void **dst) { + bitset_container_t *ans = bitset_container_create(); + int card = bitset_container_andnot(src_1, src_2, ans); + if (card <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(ans); + bitset_container_free(ans); + return false; // not bitset + } else { + *dst = ans; + return true; + } +} + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_bitset_container_iandnot(bitset_container_t *src_1, + const bitset_container_t *src_2, + void **dst) { + int card = bitset_container_andnot(src_1, src_2, src_1); + if (card <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(src_1); + bitset_container_free(src_1); + return false; // not bitset + } else { + *dst = src_1; + return true; + } +} +/* end file src/containers/mixed_andnot.c */ +/* begin file src/containers/mixed_equal.c */ + +bool array_container_equal_bitset(const array_container_t* container1, + const bitset_container_t* container2) { + if (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) { + if (container2->cardinality != container1->cardinality) { + return false; + } + } + int32_t pos = 0; + for (int32_t i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; ++i) { + uint64_t w = container2->array[i]; + while (w != 0) { + uint64_t t = w & (~w + 1); + uint16_t r = i * 64 + __builtin_ctzll(w); + if (pos >= container1->cardinality) { + return false; + } + if (container1->array[pos] != r) { + return false; + } + ++pos; + w ^= t; + } + } + return (pos == container1->cardinality); +} + +bool run_container_equals_array(const run_container_t* container1, + const array_container_t* container2) { + if (run_container_cardinality(container1) != container2->cardinality) + return false; + int32_t pos = 0; + for (int i = 0; i < container1->n_runs; ++i) { + const uint32_t run_start = container1->runs[i].value; + const uint32_t le = container1->runs[i].length; + + if (container2->array[pos] != run_start) { + return false; + } + + if (container2->array[pos + le] != run_start + le) { + return false; + } + + pos += le + 1; + } + return true; +} + +bool run_container_equals_bitset(const run_container_t* container1, + const bitset_container_t* container2) { + + int run_card = run_container_cardinality(container1); + int bitset_card = (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) ? + container2->cardinality : + bitset_container_compute_cardinality(container2); + if (bitset_card != run_card) { + return false; + } + + for (int32_t i = 0; i < container1->n_runs; i++) { + uint32_t begin = container1->runs[i].value; + if (container1->runs[i].length) { + uint32_t end = begin + container1->runs[i].length + 1; + if (!bitset_container_contains_range(container2, begin, end)) { + return false; + } + } else { + if (!bitset_container_contains(container2, begin)) { + return false; + } + } + } + + return true; +} +/* end file src/containers/mixed_equal.c */ +/* begin file src/containers/mixed_intersection.c */ +/* + * mixed_intersection.c + * + */ + + +/* Compute the intersection of src_1 and src_2 and write the result to + * dst. */ +void array_bitset_container_intersection(const array_container_t *src_1, + const bitset_container_t *src_2, + array_container_t *dst) { + if (dst->capacity < src_1->cardinality) { + array_container_grow(dst, src_1->cardinality, false); + } + int32_t newcard = 0; // dst could be src_1 + const int32_t origcard = src_1->cardinality; + for (int i = 0; i < origcard; ++i) { + uint16_t key = src_1->array[i]; + // this branchless approach is much faster... + dst->array[newcard] = key; + newcard += bitset_container_contains(src_2, key); + /** + * we could do it this way instead... + * if (bitset_container_contains(src_2, key)) { + * dst->array[newcard++] = key; + * } + * but if the result is unpredictable, the processor generates + * many mispredicted branches. + * Difference can be huge (from 3 cycles when predictable all the way + * to 16 cycles when unpredictable. + * See + * https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/extra/bitset/c/arraybitsetintersection.c + */ + } + dst->cardinality = newcard; +} + +/* Compute the size of the intersection of src_1 and src_2. */ +int array_bitset_container_intersection_cardinality( + const array_container_t *src_1, const bitset_container_t *src_2) { + int32_t newcard = 0; + const int32_t origcard = src_1->cardinality; + for (int i = 0; i < origcard; ++i) { + uint16_t key = src_1->array[i]; + newcard += bitset_container_contains(src_2, key); + } + return newcard; +} + + +bool array_bitset_container_intersect(const array_container_t *src_1, + const bitset_container_t *src_2) { + const int32_t origcard = src_1->cardinality; + for (int i = 0; i < origcard; ++i) { + uint16_t key = src_1->array[i]; + if(bitset_container_contains(src_2, key)) return true; + } + return false; +} + +/* Compute the intersection of src_1 and src_2 and write the result to + * dst. It is allowed for dst to be equal to src_1. We assume that dst is a + * valid container. */ +void array_run_container_intersection(const array_container_t *src_1, + const run_container_t *src_2, + array_container_t *dst) { + if (run_container_is_full(src_2)) { + if (dst != src_1) array_container_copy(src_1, dst); + return; + } + if (dst->capacity < src_1->cardinality) { + array_container_grow(dst, src_1->cardinality, false); + } + if (src_2->n_runs == 0) { + return; + } + int32_t rlepos = 0; + int32_t arraypos = 0; + rle16_t rle = src_2->runs[rlepos]; + int32_t newcard = 0; + while (arraypos < src_1->cardinality) { + const uint16_t arrayval = src_1->array[arraypos]; + while (rle.value + rle.length < + arrayval) { // this will frequently be false + ++rlepos; + if (rlepos == src_2->n_runs) { + dst->cardinality = newcard; + return; // we are done + } + rle = src_2->runs[rlepos]; + } + if (rle.value > arrayval) { + arraypos = advanceUntil(src_1->array, arraypos, src_1->cardinality, + rle.value); + } else { + dst->array[newcard] = arrayval; + newcard++; + arraypos++; + } + } + dst->cardinality = newcard; +} + +/* Compute the intersection of src_1 and src_2 and write the result to + * *dst. If the result is true then the result is a bitset_container_t + * otherwise is a array_container_t. If *dst == src_2, an in-place processing + * is attempted.*/ +bool run_bitset_container_intersection(const run_container_t *src_1, + const bitset_container_t *src_2, + void **dst) { + if (run_container_is_full(src_1)) { + if (*dst != src_2) *dst = bitset_container_clone(src_2); + return true; + } + int32_t card = run_container_cardinality(src_1); + if (card <= DEFAULT_MAX_SIZE) { + // result can only be an array (assuming that we never make a + // RunContainer) + if (card > src_2->cardinality) { + card = src_2->cardinality; + } + array_container_t *answer = array_container_create_given_capacity(card); + *dst = answer; + if (*dst == NULL) { + return false; + } + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + uint32_t endofrun = (uint32_t)rle.value + rle.length; + for (uint32_t runValue = rle.value; runValue <= endofrun; + ++runValue) { + answer->array[answer->cardinality] = (uint16_t)runValue; + answer->cardinality += + bitset_container_contains(src_2, runValue); + } + } + return false; + } + if (*dst == src_2) { // we attempt in-place + bitset_container_t *answer = (bitset_container_t *)*dst; + uint32_t start = 0; + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + const rle16_t rle = src_1->runs[rlepos]; + uint32_t end = rle.value; + bitset_reset_range(src_2->array, start, end); + + start = end + rle.length + 1; + } + bitset_reset_range(src_2->array, start, UINT32_C(1) << 16); + answer->cardinality = bitset_container_compute_cardinality(answer); + if (src_2->cardinality > DEFAULT_MAX_SIZE) { + return true; + } else { + array_container_t *newanswer = array_container_from_bitset(src_2); + if (newanswer == NULL) { + *dst = NULL; + return false; + } + *dst = newanswer; + return false; + } + } else { // no inplace + // we expect the answer to be a bitmap (if we are lucky) + bitset_container_t *answer = bitset_container_clone(src_2); + + *dst = answer; + if (answer == NULL) { + return true; + } + uint32_t start = 0; + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + const rle16_t rle = src_1->runs[rlepos]; + uint32_t end = rle.value; + bitset_reset_range(answer->array, start, end); + start = end + rle.length + 1; + } + bitset_reset_range(answer->array, start, UINT32_C(1) << 16); + answer->cardinality = bitset_container_compute_cardinality(answer); + + if (answer->cardinality > DEFAULT_MAX_SIZE) { + return true; + } else { + array_container_t *newanswer = array_container_from_bitset(answer); + bitset_container_free((bitset_container_t *)*dst); + if (newanswer == NULL) { + *dst = NULL; + return false; + } + *dst = newanswer; + return false; + } + } +} + +/* Compute the size of the intersection between src_1 and src_2 . */ +int array_run_container_intersection_cardinality(const array_container_t *src_1, + const run_container_t *src_2) { + if (run_container_is_full(src_2)) { + return src_1->cardinality; + } + if (src_2->n_runs == 0) { + return 0; + } + int32_t rlepos = 0; + int32_t arraypos = 0; + rle16_t rle = src_2->runs[rlepos]; + int32_t newcard = 0; + while (arraypos < src_1->cardinality) { + const uint16_t arrayval = src_1->array[arraypos]; + while (rle.value + rle.length < + arrayval) { // this will frequently be false + ++rlepos; + if (rlepos == src_2->n_runs) { + return newcard; // we are done + } + rle = src_2->runs[rlepos]; + } + if (rle.value > arrayval) { + arraypos = advanceUntil(src_1->array, arraypos, src_1->cardinality, + rle.value); + } else { + newcard++; + arraypos++; + } + } + return newcard; +} + +/* Compute the intersection between src_1 and src_2 + **/ +int run_bitset_container_intersection_cardinality( + const run_container_t *src_1, const bitset_container_t *src_2) { + if (run_container_is_full(src_1)) { + return bitset_container_cardinality(src_2); + } + int answer = 0; + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + answer += + bitset_lenrange_cardinality(src_2->array, rle.value, rle.length); + } + return answer; +} + + +bool array_run_container_intersect(const array_container_t *src_1, + const run_container_t *src_2) { + if( run_container_is_full(src_2) ) { + return !array_container_empty(src_1); + } + if (src_2->n_runs == 0) { + return false; + } + int32_t rlepos = 0; + int32_t arraypos = 0; + rle16_t rle = src_2->runs[rlepos]; + while (arraypos < src_1->cardinality) { + const uint16_t arrayval = src_1->array[arraypos]; + while (rle.value + rle.length < + arrayval) { // this will frequently be false + ++rlepos; + if (rlepos == src_2->n_runs) { + return false; // we are done + } + rle = src_2->runs[rlepos]; + } + if (rle.value > arrayval) { + arraypos = advanceUntil(src_1->array, arraypos, src_1->cardinality, + rle.value); + } else { + return true; + } + } + return false; +} + +/* Compute the intersection between src_1 and src_2 + **/ +bool run_bitset_container_intersect(const run_container_t *src_1, + const bitset_container_t *src_2) { + if( run_container_is_full(src_1) ) { + return !bitset_container_empty(src_2); + } + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + if(!bitset_lenrange_empty(src_2->array, rle.value,rle.length)) return true; + } + return false; +} + +/* + * Compute the intersection between src_1 and src_2 and write the result + * to *dst. If the return function is true, the result is a bitset_container_t + * otherwise is a array_container_t. + */ +bool bitset_bitset_container_intersection(const bitset_container_t *src_1, + const bitset_container_t *src_2, + void **dst) { + const int newCardinality = bitset_container_and_justcard(src_1, src_2); + if (newCardinality > DEFAULT_MAX_SIZE) { + *dst = bitset_container_create(); + if (*dst != NULL) { + bitset_container_and_nocard(src_1, src_2, + (bitset_container_t *)*dst); + ((bitset_container_t *)*dst)->cardinality = newCardinality; + } + return true; // it is a bitset + } + *dst = array_container_create_given_capacity(newCardinality); + if (*dst != NULL) { + ((array_container_t *)*dst)->cardinality = newCardinality; + bitset_extract_intersection_setbits_uint16( + ((const bitset_container_t *)src_1)->array, + ((const bitset_container_t *)src_2)->array, + BITSET_CONTAINER_SIZE_IN_WORDS, ((array_container_t *)*dst)->array, + 0); + } + return false; // not a bitset +} + +bool bitset_bitset_container_intersection_inplace( + bitset_container_t *src_1, const bitset_container_t *src_2, void **dst) { + const int newCardinality = bitset_container_and_justcard(src_1, src_2); + if (newCardinality > DEFAULT_MAX_SIZE) { + *dst = src_1; + bitset_container_and_nocard(src_1, src_2, src_1); + ((bitset_container_t *)*dst)->cardinality = newCardinality; + return true; // it is a bitset + } + *dst = array_container_create_given_capacity(newCardinality); + if (*dst != NULL) { + ((array_container_t *)*dst)->cardinality = newCardinality; + bitset_extract_intersection_setbits_uint16( + ((const bitset_container_t *)src_1)->array, + ((const bitset_container_t *)src_2)->array, + BITSET_CONTAINER_SIZE_IN_WORDS, ((array_container_t *)*dst)->array, + 0); + } + return false; // not a bitset +} +/* end file src/containers/mixed_intersection.c */ +/* begin file src/containers/mixed_negation.c */ +/* + * mixed_negation.c + * + */ + +#include +#include + + +// TODO: make simplified and optimized negation code across +// the full range. + +/* Negation across the entire range of the container. + * Compute the negation of src and write the result + * to *dst. The complement of a + * sufficiently sparse set will always be dense and a hence a bitmap +' * We assume that dst is pre-allocated and a valid bitset container + * There can be no in-place version. + */ +void array_container_negation(const array_container_t *src, + bitset_container_t *dst) { + uint64_t card = UINT64_C(1 << 16); + bitset_container_set_all(dst); + + dst->cardinality = (int32_t)bitset_clear_list(dst->array, card, src->array, + (uint64_t)src->cardinality); +} + +/* Negation across the entire range of the container + * Compute the negation of src and write the result + * to *dst. A true return value indicates a bitset result, + * otherwise the result is an array container. + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +bool bitset_container_negation(const bitset_container_t *src, void **dst) { + return bitset_container_negation_range(src, 0, (1 << 16), dst); +} + +/* inplace version */ +/* + * Same as bitset_container_negation except that if the output is to + * be a + * bitset_container_t, then src is modified and no allocation is made. + * If the output is to be an array_container_t, then caller is responsible + * to free the container. + * In all cases, the result is in *dst. + */ +bool bitset_container_negation_inplace(bitset_container_t *src, void **dst) { + return bitset_container_negation_range_inplace(src, 0, (1 << 16), dst); +} + +/* Negation across the entire range of container + * Compute the negation of src and write the result + * to *dst. Return values are the *_TYPECODES as defined * in containers.h + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +int run_container_negation(const run_container_t *src, void **dst) { + return run_container_negation_range(src, 0, (1 << 16), dst); +} + +/* + * Same as run_container_negation except that if the output is to + * be a + * run_container_t, and has the capacity to hold the result, + * then src is modified and no allocation is made. + * In all cases, the result is in *dst. + */ +int run_container_negation_inplace(run_container_t *src, void **dst) { + return run_container_negation_range_inplace(src, 0, (1 << 16), dst); +} + +/* Negation across a range of the container. + * Compute the negation of src and write the result + * to *dst. Returns true if the result is a bitset container + * and false for an array container. *dst is not preallocated. + */ +bool array_container_negation_range(const array_container_t *src, + const int range_start, const int range_end, + void **dst) { + /* close port of the Java implementation */ + if (range_start >= range_end) { + *dst = array_container_clone(src); + return false; + } + + int32_t start_index = + binarySearch(src->array, src->cardinality, (uint16_t)range_start); + if (start_index < 0) start_index = -start_index - 1; + + int32_t last_index = + binarySearch(src->array, src->cardinality, (uint16_t)(range_end - 1)); + if (last_index < 0) last_index = -last_index - 2; + + const int32_t current_values_in_range = last_index - start_index + 1; + const int32_t span_to_be_flipped = range_end - range_start; + const int32_t new_values_in_range = + span_to_be_flipped - current_values_in_range; + const int32_t cardinality_change = + new_values_in_range - current_values_in_range; + const int32_t new_cardinality = src->cardinality + cardinality_change; + + if (new_cardinality > DEFAULT_MAX_SIZE) { + bitset_container_t *temp = bitset_container_from_array(src); + bitset_flip_range(temp->array, (uint32_t)range_start, + (uint32_t)range_end); + temp->cardinality = new_cardinality; + *dst = temp; + return true; + } + + array_container_t *arr = + array_container_create_given_capacity(new_cardinality); + *dst = (void *)arr; + if(new_cardinality == 0) { + arr->cardinality = new_cardinality; + return false; // we are done. + } + // copy stuff before the active area + memcpy(arr->array, src->array, start_index * sizeof(uint16_t)); + + // work on the range + int32_t out_pos = start_index, in_pos = start_index; + int32_t val_in_range = range_start; + for (; val_in_range < range_end && in_pos <= last_index; ++val_in_range) { + if ((uint16_t)val_in_range != src->array[in_pos]) { + arr->array[out_pos++] = (uint16_t)val_in_range; + } else { + ++in_pos; + } + } + for (; val_in_range < range_end; ++val_in_range) + arr->array[out_pos++] = (uint16_t)val_in_range; + + // content after the active range + memcpy(arr->array + out_pos, src->array + (last_index + 1), + (src->cardinality - (last_index + 1)) * sizeof(uint16_t)); + arr->cardinality = new_cardinality; + return false; +} + +/* Even when the result would fit, it is unclear how to make an + * inplace version without inefficient copying. + */ + +bool array_container_negation_range_inplace(array_container_t *src, + const int range_start, + const int range_end, void **dst) { + bool ans = array_container_negation_range(src, range_start, range_end, dst); + // TODO : try a real inplace version + array_container_free(src); + return ans; +} + +/* Negation across a range of the container + * Compute the negation of src and write the result + * to *dst. A true return value indicates a bitset result, + * otherwise the result is an array container. + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +bool bitset_container_negation_range(const bitset_container_t *src, + const int range_start, const int range_end, + void **dst) { + // TODO maybe consider density-based estimate + // and sometimes build result directly as array, with + // conversion back to bitset if wrong. Or determine + // actual result cardinality, then go directly for the known final cont. + + // keep computation using bitsets as long as possible. + bitset_container_t *t = bitset_container_clone(src); + bitset_flip_range(t->array, (uint32_t)range_start, (uint32_t)range_end); + t->cardinality = bitset_container_compute_cardinality(t); + + if (t->cardinality > DEFAULT_MAX_SIZE) { + *dst = t; + return true; + } else { + *dst = array_container_from_bitset(t); + bitset_container_free(t); + return false; + } +} + +/* inplace version */ +/* + * Same as bitset_container_negation except that if the output is to + * be a + * bitset_container_t, then src is modified and no allocation is made. + * If the output is to be an array_container_t, then caller is responsible + * to free the container. + * In all cases, the result is in *dst. + */ +bool bitset_container_negation_range_inplace(bitset_container_t *src, + const int range_start, + const int range_end, void **dst) { + bitset_flip_range(src->array, (uint32_t)range_start, (uint32_t)range_end); + src->cardinality = bitset_container_compute_cardinality(src); + if (src->cardinality > DEFAULT_MAX_SIZE) { + *dst = src; + return true; + } + *dst = array_container_from_bitset(src); + bitset_container_free(src); + return false; +} + +/* Negation across a range of container + * Compute the negation of src and write the result + * to *dst. Return values are the *_TYPECODES as defined * in containers.h + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +int run_container_negation_range(const run_container_t *src, + const int range_start, const int range_end, + void **dst) { + uint8_t return_typecode; + + // follows the Java implementation + if (range_end <= range_start) { + *dst = run_container_clone(src); + return RUN_CONTAINER_TYPE_CODE; + } + + run_container_t *ans = run_container_create_given_capacity( + src->n_runs + 1); // src->n_runs + 1); + int k = 0; + for (; k < src->n_runs && src->runs[k].value < range_start; ++k) { + ans->runs[k] = src->runs[k]; + ans->n_runs++; + } + + run_container_smart_append_exclusive( + ans, (uint16_t)range_start, (uint16_t)(range_end - range_start - 1)); + + for (; k < src->n_runs; ++k) { + run_container_smart_append_exclusive(ans, src->runs[k].value, + src->runs[k].length); + } + + *dst = convert_run_to_efficient_container(ans, &return_typecode); + if (return_typecode != RUN_CONTAINER_TYPE_CODE) run_container_free(ans); + + return return_typecode; +} + +/* + * Same as run_container_negation except that if the output is to + * be a + * run_container_t, and has the capacity to hold the result, + * then src is modified and no allocation is made. + * In all cases, the result is in *dst. + */ +int run_container_negation_range_inplace(run_container_t *src, + const int range_start, + const int range_end, void **dst) { + uint8_t return_typecode; + + if (range_end <= range_start) { + *dst = src; + return RUN_CONTAINER_TYPE_CODE; + } + + // TODO: efficient special case when range is 0 to 65535 inclusive + + if (src->capacity == src->n_runs) { + // no excess room. More checking to see if result can fit + bool last_val_before_range = false; + bool first_val_in_range = false; + bool last_val_in_range = false; + bool first_val_past_range = false; + + if (range_start > 0) + last_val_before_range = + run_container_contains(src, (uint16_t)(range_start - 1)); + first_val_in_range = run_container_contains(src, (uint16_t)range_start); + + if (last_val_before_range == first_val_in_range) { + last_val_in_range = + run_container_contains(src, (uint16_t)(range_end - 1)); + if (range_end != 0x10000) + first_val_past_range = + run_container_contains(src, (uint16_t)range_end); + + if (last_val_in_range == + first_val_past_range) { // no space for inplace + int ans = run_container_negation_range(src, range_start, + range_end, dst); + run_container_free(src); + return ans; + } + } + } + // all other cases: result will fit + + run_container_t *ans = src; + int my_nbr_runs = src->n_runs; + + ans->n_runs = 0; + int k = 0; + for (; (k < my_nbr_runs) && (src->runs[k].value < range_start); ++k) { + // ans->runs[k] = src->runs[k]; (would be self-copy) + ans->n_runs++; + } + + // as with Java implementation, use locals to give self a buffer of depth 1 + rle16_t buffered = (rle16_t){.value = (uint16_t)0, .length = (uint16_t)0}; + rle16_t next = buffered; + if (k < my_nbr_runs) buffered = src->runs[k]; + + run_container_smart_append_exclusive( + ans, (uint16_t)range_start, (uint16_t)(range_end - range_start - 1)); + + for (; k < my_nbr_runs; ++k) { + if (k + 1 < my_nbr_runs) next = src->runs[k + 1]; + + run_container_smart_append_exclusive(ans, buffered.value, + buffered.length); + buffered = next; + } + + *dst = convert_run_to_efficient_container(ans, &return_typecode); + if (return_typecode != RUN_CONTAINER_TYPE_CODE) run_container_free(ans); + + return return_typecode; +} +/* end file src/containers/mixed_negation.c */ +/* begin file src/containers/mixed_subset.c */ + +bool array_container_is_subset_bitset(const array_container_t* container1, + const bitset_container_t* container2) { + if (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) { + if (container2->cardinality < container1->cardinality) { + return false; + } + } + for (int i = 0; i < container1->cardinality; ++i) { + if (!bitset_container_contains(container2, container1->array[i])) { + return false; + } + } + return true; +} + +bool run_container_is_subset_array(const run_container_t* container1, + const array_container_t* container2) { + if (run_container_cardinality(container1) > container2->cardinality) + return false; + int32_t start_pos = -1, stop_pos = -1; + for (int i = 0; i < container1->n_runs; ++i) { + int32_t start = container1->runs[i].value; + int32_t stop = start + container1->runs[i].length; + start_pos = advanceUntil(container2->array, stop_pos, + container2->cardinality, start); + stop_pos = advanceUntil(container2->array, stop_pos, + container2->cardinality, stop); + if (start_pos == container2->cardinality) { + return false; + } else if (stop_pos - start_pos != stop - start || + container2->array[start_pos] != start || + container2->array[stop_pos] != stop) { + return false; + } + } + return true; +} + +bool array_container_is_subset_run(const array_container_t* container1, + const run_container_t* container2) { + if (container1->cardinality > run_container_cardinality(container2)) + return false; + int i_array = 0, i_run = 0; + while (i_array < container1->cardinality && i_run < container2->n_runs) { + uint32_t start = container2->runs[i_run].value; + uint32_t stop = start + container2->runs[i_run].length; + if (container1->array[i_array] < start) { + return false; + } else if (container1->array[i_array] > stop) { + i_run++; + } else { // the value of the array is in the run + i_array++; + } + } + if (i_array == container1->cardinality) { + return true; + } else { + return false; + } +} + +bool run_container_is_subset_bitset(const run_container_t* container1, + const bitset_container_t* container2) { + // todo: this code could be much faster + if (container2->cardinality != BITSET_UNKNOWN_CARDINALITY) { + if (container2->cardinality < run_container_cardinality(container1)) { + return false; + } + } else { + int32_t card = bitset_container_compute_cardinality( + container2); // modify container2? + if (card < run_container_cardinality(container1)) { + return false; + } + } + for (int i = 0; i < container1->n_runs; ++i) { + uint32_t run_start = container1->runs[i].value; + uint32_t le = container1->runs[i].length; + for (uint32_t j = run_start; j <= run_start + le; ++j) { + if (!bitset_container_contains(container2, j)) { + return false; + } + } + } + return true; +} + +bool bitset_container_is_subset_run(const bitset_container_t* container1, + const run_container_t* container2) { + // todo: this code could be much faster + if (container1->cardinality != BITSET_UNKNOWN_CARDINALITY) { + if (container1->cardinality > run_container_cardinality(container2)) { + return false; + } + } + int32_t i_bitset = 0, i_run = 0; + while (i_bitset < BITSET_CONTAINER_SIZE_IN_WORDS && + i_run < container2->n_runs) { + uint64_t w = container1->array[i_bitset]; + while (w != 0 && i_run < container2->n_runs) { + uint32_t start = container2->runs[i_run].value; + uint32_t stop = start + container2->runs[i_run].length; + uint64_t t = w & (~w + 1); + uint16_t r = i_bitset * 64 + __builtin_ctzll(w); + if (r < start) { + return false; + } else if (r > stop) { + i_run++; + continue; + } else { + w ^= t; + } + } + if (w == 0) { + i_bitset++; + } else { + return false; + } + } + if (i_bitset < BITSET_CONTAINER_SIZE_IN_WORDS) { + // terminated iterating on the run containers, check that rest of bitset + // is empty + for (; i_bitset < BITSET_CONTAINER_SIZE_IN_WORDS; i_bitset++) { + if (container1->array[i_bitset] != 0) { + return false; + } + } + } + return true; +} +/* end file src/containers/mixed_subset.c */ +/* begin file src/containers/mixed_union.c */ +/* + * mixed_union.c + * + */ + +#include +#include + + +/* Compute the union of src_1 and src_2 and write the result to + * dst. */ +void array_bitset_container_union(const array_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst) { + if (src_2 != dst) bitset_container_copy(src_2, dst); + dst->cardinality = (int32_t)bitset_set_list_withcard( + dst->array, dst->cardinality, src_1->array, src_1->cardinality); +} + +/* Compute the union of src_1 and src_2 and write the result to + * dst. It is allowed for src_2 to be dst. This version does not + * update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). */ +void array_bitset_container_lazy_union(const array_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst) { + if (src_2 != dst) bitset_container_copy(src_2, dst); + bitset_set_list(dst->array, src_1->array, src_1->cardinality); + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; +} + +void run_bitset_container_union(const run_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst) { + assert(!run_container_is_full(src_1)); // catch this case upstream + if (src_2 != dst) bitset_container_copy(src_2, dst); + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + bitset_set_lenrange(dst->array, rle.value, rle.length); + } + dst->cardinality = bitset_container_compute_cardinality(dst); +} + +void run_bitset_container_lazy_union(const run_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst) { + assert(!run_container_is_full(src_1)); // catch this case upstream + if (src_2 != dst) bitset_container_copy(src_2, dst); + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + bitset_set_lenrange(dst->array, rle.value, rle.length); + } + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; +} + +// why do we leave the result as a run container?? +void array_run_container_union(const array_container_t *src_1, + const run_container_t *src_2, + run_container_t *dst) { + if (run_container_is_full(src_2)) { + run_container_copy(src_2, dst); + return; + } + // TODO: see whether the "2*" is spurious + run_container_grow(dst, 2 * (src_1->cardinality + src_2->n_runs), false); + int32_t rlepos = 0; + int32_t arraypos = 0; + rle16_t previousrle; + if (src_2->runs[rlepos].value <= src_1->array[arraypos]) { + previousrle = run_container_append_first(dst, src_2->runs[rlepos]); + rlepos++; + } else { + previousrle = + run_container_append_value_first(dst, src_1->array[arraypos]); + arraypos++; + } + while ((rlepos < src_2->n_runs) && (arraypos < src_1->cardinality)) { + if (src_2->runs[rlepos].value <= src_1->array[arraypos]) { + run_container_append(dst, src_2->runs[rlepos], &previousrle); + rlepos++; + } else { + run_container_append_value(dst, src_1->array[arraypos], + &previousrle); + arraypos++; + } + } + if (arraypos < src_1->cardinality) { + while (arraypos < src_1->cardinality) { + run_container_append_value(dst, src_1->array[arraypos], + &previousrle); + arraypos++; + } + } else { + while (rlepos < src_2->n_runs) { + run_container_append(dst, src_2->runs[rlepos], &previousrle); + rlepos++; + } + } +} + +void array_run_container_inplace_union(const array_container_t *src_1, + run_container_t *src_2) { + if (run_container_is_full(src_2)) { + return; + } + const int32_t maxoutput = src_1->cardinality + src_2->n_runs; + const int32_t neededcapacity = maxoutput + src_2->n_runs; + if (src_2->capacity < neededcapacity) + run_container_grow(src_2, neededcapacity, true); + memmove(src_2->runs + maxoutput, src_2->runs, + src_2->n_runs * sizeof(rle16_t)); + rle16_t *inputsrc2 = src_2->runs + maxoutput; + int32_t rlepos = 0; + int32_t arraypos = 0; + int src2nruns = src_2->n_runs; + src_2->n_runs = 0; + + rle16_t previousrle; + + if (inputsrc2[rlepos].value <= src_1->array[arraypos]) { + previousrle = run_container_append_first(src_2, inputsrc2[rlepos]); + rlepos++; + } else { + previousrle = + run_container_append_value_first(src_2, src_1->array[arraypos]); + arraypos++; + } + + while ((rlepos < src2nruns) && (arraypos < src_1->cardinality)) { + if (inputsrc2[rlepos].value <= src_1->array[arraypos]) { + run_container_append(src_2, inputsrc2[rlepos], &previousrle); + rlepos++; + } else { + run_container_append_value(src_2, src_1->array[arraypos], + &previousrle); + arraypos++; + } + } + if (arraypos < src_1->cardinality) { + while (arraypos < src_1->cardinality) { + run_container_append_value(src_2, src_1->array[arraypos], + &previousrle); + arraypos++; + } + } else { + while (rlepos < src2nruns) { + run_container_append(src_2, inputsrc2[rlepos], &previousrle); + rlepos++; + } + } +} + +bool array_array_container_union(const array_container_t *src_1, + const array_container_t *src_2, void **dst) { + int totalCardinality = src_1->cardinality + src_2->cardinality; + if (totalCardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_create_given_capacity(totalCardinality); + if (*dst != NULL) { + array_container_union(src_1, src_2, (array_container_t *)*dst); + } else { + return true; // otherwise failure won't be caught + } + return false; // not a bitset + } + *dst = bitset_container_create(); + bool returnval = true; // expect a bitset + if (*dst != NULL) { + bitset_container_t *ourbitset = (bitset_container_t *)*dst; + bitset_set_list(ourbitset->array, src_1->array, src_1->cardinality); + ourbitset->cardinality = (int32_t)bitset_set_list_withcard( + ourbitset->array, src_1->cardinality, src_2->array, + src_2->cardinality); + if (ourbitset->cardinality <= DEFAULT_MAX_SIZE) { + // need to convert! + *dst = array_container_from_bitset(ourbitset); + bitset_container_free(ourbitset); + returnval = false; // not going to be a bitset + } + } + return returnval; +} + +bool array_array_container_inplace_union(array_container_t *src_1, + const array_container_t *src_2, void **dst) { + int totalCardinality = src_1->cardinality + src_2->cardinality; + *dst = NULL; + if (totalCardinality <= DEFAULT_MAX_SIZE) { + if(src_1->capacity < totalCardinality) { + *dst = array_container_create_given_capacity(2 * totalCardinality); // be purposefully generous + if (*dst != NULL) { + array_container_union(src_1, src_2, (array_container_t *)*dst); + } else { + return true; // otherwise failure won't be caught + } + return false; // not a bitset + } else { + memmove(src_1->array + src_2->cardinality, src_1->array, src_1->cardinality * sizeof(uint16_t)); + src_1->cardinality = (int32_t)union_uint16(src_1->array + src_2->cardinality, src_1->cardinality, + src_2->array, src_2->cardinality, src_1->array); + return false; // not a bitset + } + } + *dst = bitset_container_create(); + bool returnval = true; // expect a bitset + if (*dst != NULL) { + bitset_container_t *ourbitset = (bitset_container_t *)*dst; + bitset_set_list(ourbitset->array, src_1->array, src_1->cardinality); + ourbitset->cardinality = (int32_t)bitset_set_list_withcard( + ourbitset->array, src_1->cardinality, src_2->array, + src_2->cardinality); + if (ourbitset->cardinality <= DEFAULT_MAX_SIZE) { + // need to convert! + if(src_1->capacity < ourbitset->cardinality) { + array_container_grow(src_1, ourbitset->cardinality, false); + } + + bitset_extract_setbits_uint16(ourbitset->array, BITSET_CONTAINER_SIZE_IN_WORDS, + src_1->array, 0); + src_1->cardinality = ourbitset->cardinality; + *dst = src_1; + bitset_container_free(ourbitset); + returnval = false; // not going to be a bitset + } + } + return returnval; +} + + +bool array_array_container_lazy_union(const array_container_t *src_1, + const array_container_t *src_2, + void **dst) { + int totalCardinality = src_1->cardinality + src_2->cardinality; + if (totalCardinality <= ARRAY_LAZY_LOWERBOUND) { + *dst = array_container_create_given_capacity(totalCardinality); + if (*dst != NULL) { + array_container_union(src_1, src_2, (array_container_t *)*dst); + } else { + return true; // otherwise failure won't be caught + } + return false; // not a bitset + } + *dst = bitset_container_create(); + bool returnval = true; // expect a bitset + if (*dst != NULL) { + bitset_container_t *ourbitset = (bitset_container_t *)*dst; + bitset_set_list(ourbitset->array, src_1->array, src_1->cardinality); + bitset_set_list(ourbitset->array, src_2->array, src_2->cardinality); + ourbitset->cardinality = BITSET_UNKNOWN_CARDINALITY; + } + return returnval; +} + + +bool array_array_container_lazy_inplace_union(array_container_t *src_1, + const array_container_t *src_2, + void **dst) { + int totalCardinality = src_1->cardinality + src_2->cardinality; + *dst = NULL; + if (totalCardinality <= ARRAY_LAZY_LOWERBOUND) { + if(src_1->capacity < totalCardinality) { + *dst = array_container_create_given_capacity(2 * totalCardinality); // be purposefully generous + if (*dst != NULL) { + array_container_union(src_1, src_2, (array_container_t *)*dst); + } else { + return true; // otherwise failure won't be caught + } + return false; // not a bitset + } else { + memmove(src_1->array + src_2->cardinality, src_1->array, src_1->cardinality * sizeof(uint16_t)); + src_1->cardinality = (int32_t)union_uint16(src_1->array + src_2->cardinality, src_1->cardinality, + src_2->array, src_2->cardinality, src_1->array); + return false; // not a bitset + } + } + *dst = bitset_container_create(); + bool returnval = true; // expect a bitset + if (*dst != NULL) { + bitset_container_t *ourbitset = (bitset_container_t *)*dst; + bitset_set_list(ourbitset->array, src_1->array, src_1->cardinality); + bitset_set_list(ourbitset->array, src_2->array, src_2->cardinality); + ourbitset->cardinality = BITSET_UNKNOWN_CARDINALITY; + } + return returnval; +} +/* end file src/containers/mixed_union.c */ +/* begin file src/containers/mixed_xor.c */ +/* + * mixed_xor.c + */ + +#include +#include + + +/* Compute the xor of src_1 and src_2 and write the result to + * dst (which has no container initially). + * Result is true iff dst is a bitset */ +bool array_bitset_container_xor(const array_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + bitset_container_t *result = bitset_container_create(); + bitset_container_copy(src_2, result); + result->cardinality = (int32_t)bitset_flip_list_withcard( + result->array, result->cardinality, src_1->array, src_1->cardinality); + + // do required type conversions. + if (result->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(result); + bitset_container_free(result); + return false; // not bitset + } + *dst = result; + return true; // bitset +} + +/* Compute the xor of src_1 and src_2 and write the result to + * dst. It is allowed for src_2 to be dst. This version does not + * update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). + */ + +void array_bitset_container_lazy_xor(const array_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst) { + if (src_2 != dst) bitset_container_copy(src_2, dst); + bitset_flip_list(dst->array, src_1->array, src_1->cardinality); + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; +} + +/* Compute the xor of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_xor(const run_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + bitset_container_t *result = bitset_container_create(); + + bitset_container_copy(src_2, result); + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + bitset_flip_range(result->array, rle.value, + rle.value + rle.length + UINT32_C(1)); + } + result->cardinality = bitset_container_compute_cardinality(result); + + if (result->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(result); + bitset_container_free(result); + return false; // not bitset + } + *dst = result; + return true; // bitset +} + +/* lazy xor. Dst is initialized and may be equal to src_2. + * Result is left as a bitset container, even if actual + * cardinality would dictate an array container. + */ + +void run_bitset_container_lazy_xor(const run_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst) { + if (src_2 != dst) bitset_container_copy(src_2, dst); + for (int32_t rlepos = 0; rlepos < src_1->n_runs; ++rlepos) { + rle16_t rle = src_1->runs[rlepos]; + bitset_flip_range(dst->array, rle.value, + rle.value + rle.length + UINT32_C(1)); + } + dst->cardinality = BITSET_UNKNOWN_CARDINALITY; +} + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int array_run_container_xor(const array_container_t *src_1, + const run_container_t *src_2, void **dst) { + // semi following Java XOR implementation as of May 2016 + // the C OR implementation works quite differently and can return a run + // container + // TODO could optimize for full run containers. + + // use of lazy following Java impl. + const int arbitrary_threshold = 32; + if (src_1->cardinality < arbitrary_threshold) { + run_container_t *ans = run_container_create(); + array_run_container_lazy_xor(src_1, src_2, ans); // keeps runs. + uint8_t typecode_after; + *dst = + convert_run_to_efficient_container_and_free(ans, &typecode_after); + return typecode_after; + } + + int card = run_container_cardinality(src_2); + if (card <= DEFAULT_MAX_SIZE) { + // Java implementation works with the array, xoring the run elements via + // iterator + array_container_t *temp = array_container_from_run(src_2); + bool ret_is_bitset = array_array_container_xor(temp, src_1, dst); + array_container_free(temp); + return ret_is_bitset ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + + } else { // guess that it will end up as a bitset + bitset_container_t *result = bitset_container_from_run(src_2); + bool is_bitset = bitset_array_container_ixor(result, src_1, dst); + // any necessary type conversion has been done by the ixor + int retval = (is_bitset ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE); + return retval; + } +} + +/* Dst is a valid run container. (Can it be src_2? Let's say not.) + * Leaves result as run container, even if other options are + * smaller. + */ + +void array_run_container_lazy_xor(const array_container_t *src_1, + const run_container_t *src_2, + run_container_t *dst) { + run_container_grow(dst, src_1->cardinality + src_2->n_runs, false); + int32_t rlepos = 0; + int32_t arraypos = 0; + dst->n_runs = 0; + + while ((rlepos < src_2->n_runs) && (arraypos < src_1->cardinality)) { + if (src_2->runs[rlepos].value <= src_1->array[arraypos]) { + run_container_smart_append_exclusive(dst, src_2->runs[rlepos].value, + src_2->runs[rlepos].length); + rlepos++; + } else { + run_container_smart_append_exclusive(dst, src_1->array[arraypos], + 0); + arraypos++; + } + } + while (arraypos < src_1->cardinality) { + run_container_smart_append_exclusive(dst, src_1->array[arraypos], 0); + arraypos++; + } + while (rlepos < src_2->n_runs) { + run_container_smart_append_exclusive(dst, src_2->runs[rlepos].value, + src_2->runs[rlepos].length); + rlepos++; + } +} + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int run_run_container_xor(const run_container_t *src_1, + const run_container_t *src_2, void **dst) { + run_container_t *ans = run_container_create(); + run_container_xor(src_1, src_2, ans); + uint8_t typecode_after; + *dst = convert_run_to_efficient_container_and_free(ans, &typecode_after); + return typecode_after; +} + +/* + * Java implementation (as of May 2016) for array_run, run_run + * and bitset_run don't do anything different for inplace. + * Could adopt the mixed_union.c approach instead (ie, using + * smart_append_exclusive) + * + */ + +bool array_array_container_xor(const array_container_t *src_1, + const array_container_t *src_2, void **dst) { + int totalCardinality = + src_1->cardinality + src_2->cardinality; // upper bound + if (totalCardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_create_given_capacity(totalCardinality); + array_container_xor(src_1, src_2, (array_container_t *)*dst); + return false; // not a bitset + } + *dst = bitset_container_from_array(src_1); + bool returnval = true; // expect a bitset + bitset_container_t *ourbitset = (bitset_container_t *)*dst; + ourbitset->cardinality = (uint32_t)bitset_flip_list_withcard( + ourbitset->array, src_1->cardinality, src_2->array, src_2->cardinality); + if (ourbitset->cardinality <= DEFAULT_MAX_SIZE) { + // need to convert! + *dst = array_container_from_bitset(ourbitset); + bitset_container_free(ourbitset); + returnval = false; // not going to be a bitset + } + + return returnval; +} + +bool array_array_container_lazy_xor(const array_container_t *src_1, + const array_container_t *src_2, + void **dst) { + int totalCardinality = src_1->cardinality + src_2->cardinality; + // upper bound, but probably poor estimate for xor + if (totalCardinality <= ARRAY_LAZY_LOWERBOUND) { + *dst = array_container_create_given_capacity(totalCardinality); + if (*dst != NULL) + array_container_xor(src_1, src_2, (array_container_t *)*dst); + return false; // not a bitset + } + *dst = bitset_container_from_array(src_1); + bool returnval = true; // expect a bitset (maybe, for XOR??) + if (*dst != NULL) { + bitset_container_t *ourbitset = (bitset_container_t *)*dst; + bitset_flip_list(ourbitset->array, src_2->array, src_2->cardinality); + ourbitset->cardinality = BITSET_UNKNOWN_CARDINALITY; + } + return returnval; +} + +/* Compute the xor of src_1 and src_2 and write the result to + * dst (which has no container initially). Return value is + * "dst is a bitset" + */ + +bool bitset_bitset_container_xor(const bitset_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + bitset_container_t *ans = bitset_container_create(); + int card = bitset_container_xor(src_1, src_2, ans); + if (card <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(ans); + bitset_container_free(ans); + return false; // not bitset + } else { + *dst = ans; + return true; + } +} + +/* Compute the xor of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_array_container_ixor(bitset_container_t *src_1, + const array_container_t *src_2, void **dst) { + *dst = src_1; + src_1->cardinality = (uint32_t)bitset_flip_list_withcard( + src_1->array, src_1->cardinality, src_2->array, src_2->cardinality); + + if (src_1->cardinality <= DEFAULT_MAX_SIZE) { + *dst = array_container_from_bitset(src_1); + bitset_container_free(src_1); + return false; // not bitset + } else + return true; +} + +/* a bunch of in-place, some of which may not *really* be inplace. + * TODO: write actual inplace routine if efficiency warrants it + * Anything inplace with a bitset is a good candidate + */ + +bool bitset_bitset_container_ixor(bitset_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + bool ans = bitset_bitset_container_xor(src_1, src_2, dst); + bitset_container_free(src_1); + return ans; +} + +bool array_bitset_container_ixor(array_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + bool ans = array_bitset_container_xor(src_1, src_2, dst); + array_container_free(src_1); + return ans; +} + +/* Compute the xor of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_ixor(run_container_t *src_1, + const bitset_container_t *src_2, void **dst) { + bool ans = run_bitset_container_xor(src_1, src_2, dst); + run_container_free(src_1); + return ans; +} + +bool bitset_run_container_ixor(bitset_container_t *src_1, + const run_container_t *src_2, void **dst) { + bool ans = run_bitset_container_xor(src_2, src_1, dst); + bitset_container_free(src_1); + return ans; +} + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int array_run_container_ixor(array_container_t *src_1, + const run_container_t *src_2, void **dst) { + int ans = array_run_container_xor(src_1, src_2, dst); + array_container_free(src_1); + return ans; +} + +int run_array_container_ixor(run_container_t *src_1, + const array_container_t *src_2, void **dst) { + int ans = array_run_container_xor(src_2, src_1, dst); + run_container_free(src_1); + return ans; +} + +bool array_array_container_ixor(array_container_t *src_1, + const array_container_t *src_2, void **dst) { + bool ans = array_array_container_xor(src_1, src_2, dst); + array_container_free(src_1); + return ans; +} + +int run_run_container_ixor(run_container_t *src_1, const run_container_t *src_2, + void **dst) { + int ans = run_run_container_xor(src_1, src_2, dst); + run_container_free(src_1); + return ans; +} +/* end file src/containers/mixed_xor.c */ +/* begin file src/containers/run.c */ +#include +#include + + +bool run_container_add(run_container_t *run, uint16_t pos) { + int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos); + if (index >= 0) return false; // already there + index = -index - 2; // points to preceding value, possibly -1 + if (index >= 0) { // possible match + int32_t offset = pos - run->runs[index].value; + int32_t le = run->runs[index].length; + if (offset <= le) return false; // already there + if (offset == le + 1) { + // we may need to fuse + if (index + 1 < run->n_runs) { + if (run->runs[index + 1].value == pos + 1) { + // indeed fusion is needed + run->runs[index].length = run->runs[index + 1].value + + run->runs[index + 1].length - + run->runs[index].value; + recoverRoomAtIndex(run, (uint16_t)(index + 1)); + return true; + } + } + run->runs[index].length++; + return true; + } + if (index + 1 < run->n_runs) { + // we may need to fuse + if (run->runs[index + 1].value == pos + 1) { + // indeed fusion is needed + run->runs[index + 1].value = pos; + run->runs[index + 1].length = run->runs[index + 1].length + 1; + return true; + } + } + } + if (index == -1) { + // we may need to extend the first run + if (0 < run->n_runs) { + if (run->runs[0].value == pos + 1) { + run->runs[0].length++; + run->runs[0].value--; + return true; + } + } + } + makeRoomAtIndex(run, (uint16_t)(index + 1)); + run->runs[index + 1].value = pos; + run->runs[index + 1].length = 0; + return true; +} + +/* Create a new run container. Return NULL in case of failure. */ +run_container_t *run_container_create_given_capacity(int32_t size) { + run_container_t *run; + /* Allocate the run container itself. */ + run = (run_container_t *)malloc(sizeof(run_container_t)); + assert (run); + if (size <= 0) // we don't want to rely on malloc(0) + run->runs = NULL; + run->runs = (rle16_t *)malloc(sizeof(rle16_t) * size); + assert (run->runs); + run->capacity = size; + run->n_runs = 0; + return run; +} + +int run_container_shrink_to_fit(run_container_t *src) { + if (src->n_runs == src->capacity) return 0; // nothing to do + int savings = src->capacity - src->n_runs; + src->capacity = src->n_runs; + rle16_t *oldruns = src->runs; + src->runs = (rle16_t *)realloc(oldruns, src->capacity * sizeof(rle16_t)); + return savings; +} +/* Create a new run container. Return NULL in case of failure. */ +run_container_t *run_container_create(void) { + return run_container_create_given_capacity(RUN_DEFAULT_INIT_SIZE); +} + +run_container_t *run_container_clone(const run_container_t *src) { + run_container_t *run = run_container_create_given_capacity(src->capacity); + if (run == NULL) return NULL; + run->capacity = src->capacity; + run->n_runs = src->n_runs; + memcpy(run->runs, src->runs, src->n_runs * sizeof(rle16_t)); + return run; +} + +/* Free memory. */ +void run_container_free(run_container_t *run) { + if(run->runs != NULL) {// Jon Strabala reports that some tools complain otherwise + free(run->runs); + run->runs = NULL; // pedantic + } + free(run); +} + +void run_container_grow(run_container_t *run, int32_t min, bool copy) { + int32_t newCapacity = + (run->capacity == 0) + ? RUN_DEFAULT_INIT_SIZE + : run->capacity < 64 ? run->capacity * 2 + : run->capacity < 1024 ? run->capacity * 3 / 2 + : run->capacity * 5 / 4; + if (newCapacity < min) newCapacity = min; + run->capacity = newCapacity; + assert(run->capacity >= min); + if (copy) { + rle16_t *oldruns = run->runs; + run->runs = + (rle16_t *)realloc(oldruns, run->capacity * sizeof(rle16_t)); + } else { + // Jon Strabala reports that some tools complain otherwise + if (run->runs != NULL) { + free(run->runs); + } + run->runs = (rle16_t *)malloc(run->capacity * sizeof(rle16_t)); + } + // handle the case where realloc fails + if (run->runs == NULL) { + fprintf(stderr, "could not allocate memory\n"); + } + assert(run->runs != NULL); +} + +/* copy one container into another */ +void run_container_copy(const run_container_t *src, run_container_t *dst) { + const int32_t n_runs = src->n_runs; + if (src->n_runs > dst->capacity) { + run_container_grow(dst, n_runs, false); + } + dst->n_runs = n_runs; + memcpy(dst->runs, src->runs, sizeof(rle16_t) * n_runs); +} + +/* Compute the union of `src_1' and `src_2' and write the result to `dst' + * It is assumed that `dst' is distinct from both `src_1' and `src_2'. */ +void run_container_union(const run_container_t *src_1, + const run_container_t *src_2, run_container_t *dst) { + // TODO: this could be a lot more efficient + + // we start out with inexpensive checks + const bool if1 = run_container_is_full(src_1); + const bool if2 = run_container_is_full(src_2); + if (if1 || if2) { + if (if1) { + run_container_copy(src_1, dst); + return; + } + if (if2) { + run_container_copy(src_2, dst); + return; + } + } + const int32_t neededcapacity = src_1->n_runs + src_2->n_runs; + if (dst->capacity < neededcapacity) + run_container_grow(dst, neededcapacity, false); + dst->n_runs = 0; + int32_t rlepos = 0; + int32_t xrlepos = 0; + + rle16_t previousrle; + if (src_1->runs[rlepos].value <= src_2->runs[xrlepos].value) { + previousrle = run_container_append_first(dst, src_1->runs[rlepos]); + rlepos++; + } else { + previousrle = run_container_append_first(dst, src_2->runs[xrlepos]); + xrlepos++; + } + + while ((xrlepos < src_2->n_runs) && (rlepos < src_1->n_runs)) { + rle16_t newrl; + if (src_1->runs[rlepos].value <= src_2->runs[xrlepos].value) { + newrl = src_1->runs[rlepos]; + rlepos++; + } else { + newrl = src_2->runs[xrlepos]; + xrlepos++; + } + run_container_append(dst, newrl, &previousrle); + } + while (xrlepos < src_2->n_runs) { + run_container_append(dst, src_2->runs[xrlepos], &previousrle); + xrlepos++; + } + while (rlepos < src_1->n_runs) { + run_container_append(dst, src_1->runs[rlepos], &previousrle); + rlepos++; + } +} + +/* Compute the union of `src_1' and `src_2' and write the result to `src_1' + */ +void run_container_union_inplace(run_container_t *src_1, + const run_container_t *src_2) { + // TODO: this could be a lot more efficient + + // we start out with inexpensive checks + const bool if1 = run_container_is_full(src_1); + const bool if2 = run_container_is_full(src_2); + if (if1 || if2) { + if (if1) { + return; + } + if (if2) { + run_container_copy(src_2, src_1); + return; + } + } + // we move the data to the end of the current array + const int32_t maxoutput = src_1->n_runs + src_2->n_runs; + const int32_t neededcapacity = maxoutput + src_1->n_runs; + if (src_1->capacity < neededcapacity) + run_container_grow(src_1, neededcapacity, true); + memmove(src_1->runs + maxoutput, src_1->runs, + src_1->n_runs * sizeof(rle16_t)); + rle16_t *inputsrc1 = src_1->runs + maxoutput; + const int32_t input1nruns = src_1->n_runs; + src_1->n_runs = 0; + int32_t rlepos = 0; + int32_t xrlepos = 0; + + rle16_t previousrle; + if (inputsrc1[rlepos].value <= src_2->runs[xrlepos].value) { + previousrle = run_container_append_first(src_1, inputsrc1[rlepos]); + rlepos++; + } else { + previousrle = run_container_append_first(src_1, src_2->runs[xrlepos]); + xrlepos++; + } + while ((xrlepos < src_2->n_runs) && (rlepos < input1nruns)) { + rle16_t newrl; + if (inputsrc1[rlepos].value <= src_2->runs[xrlepos].value) { + newrl = inputsrc1[rlepos]; + rlepos++; + } else { + newrl = src_2->runs[xrlepos]; + xrlepos++; + } + run_container_append(src_1, newrl, &previousrle); + } + while (xrlepos < src_2->n_runs) { + run_container_append(src_1, src_2->runs[xrlepos], &previousrle); + xrlepos++; + } + while (rlepos < input1nruns) { + run_container_append(src_1, inputsrc1[rlepos], &previousrle); + rlepos++; + } +} + +/* Compute the symmetric difference of `src_1' and `src_2' and write the result + * to `dst' + * It is assumed that `dst' is distinct from both `src_1' and `src_2'. */ +void run_container_xor(const run_container_t *src_1, + const run_container_t *src_2, run_container_t *dst) { + // don't bother to convert xor with full range into negation + // since negation is implemented similarly + + const int32_t neededcapacity = src_1->n_runs + src_2->n_runs; + if (dst->capacity < neededcapacity) + run_container_grow(dst, neededcapacity, false); + + int32_t pos1 = 0; + int32_t pos2 = 0; + dst->n_runs = 0; + + while ((pos1 < src_1->n_runs) && (pos2 < src_2->n_runs)) { + if (src_1->runs[pos1].value <= src_2->runs[pos2].value) { + run_container_smart_append_exclusive(dst, src_1->runs[pos1].value, + src_1->runs[pos1].length); + pos1++; + } else { + run_container_smart_append_exclusive(dst, src_2->runs[pos2].value, + src_2->runs[pos2].length); + pos2++; + } + } + while (pos1 < src_1->n_runs) { + run_container_smart_append_exclusive(dst, src_1->runs[pos1].value, + src_1->runs[pos1].length); + pos1++; + } + + while (pos2 < src_2->n_runs) { + run_container_smart_append_exclusive(dst, src_2->runs[pos2].value, + src_2->runs[pos2].length); + pos2++; + } +} + +/* Compute the intersection of src_1 and src_2 and write the result to + * dst. It is assumed that dst is distinct from both src_1 and src_2. */ +void run_container_intersection(const run_container_t *src_1, + const run_container_t *src_2, + run_container_t *dst) { + const bool if1 = run_container_is_full(src_1); + const bool if2 = run_container_is_full(src_2); + if (if1 || if2) { + if (if1) { + run_container_copy(src_2, dst); + return; + } + if (if2) { + run_container_copy(src_1, dst); + return; + } + } + // TODO: this could be a lot more efficient, could use SIMD optimizations + const int32_t neededcapacity = src_1->n_runs + src_2->n_runs; + if (dst->capacity < neededcapacity) + run_container_grow(dst, neededcapacity, false); + dst->n_runs = 0; + int32_t rlepos = 0; + int32_t xrlepos = 0; + int32_t start = src_1->runs[rlepos].value; + int32_t end = start + src_1->runs[rlepos].length + 1; + int32_t xstart = src_2->runs[xrlepos].value; + int32_t xend = xstart + src_2->runs[xrlepos].length + 1; + while ((rlepos < src_1->n_runs) && (xrlepos < src_2->n_runs)) { + if (end <= xstart) { + ++rlepos; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + } else if (xend <= start) { + ++xrlepos; + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } else { // they overlap + const int32_t lateststart = start > xstart ? start : xstart; + int32_t earliestend; + if (end == xend) { // improbable + earliestend = end; + rlepos++; + xrlepos++; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } else if (end < xend) { + earliestend = end; + rlepos++; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + + } else { // end > xend + earliestend = xend; + xrlepos++; + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } + dst->runs[dst->n_runs].value = (uint16_t)lateststart; + dst->runs[dst->n_runs].length = + (uint16_t)(earliestend - lateststart - 1); + dst->n_runs++; + } + } +} + +/* Compute the size of the intersection of src_1 and src_2 . */ +int run_container_intersection_cardinality(const run_container_t *src_1, + const run_container_t *src_2) { + const bool if1 = run_container_is_full(src_1); + const bool if2 = run_container_is_full(src_2); + if (if1 || if2) { + if (if1) { + return run_container_cardinality(src_2); + } + if (if2) { + return run_container_cardinality(src_1); + } + } + int answer = 0; + int32_t rlepos = 0; + int32_t xrlepos = 0; + int32_t start = src_1->runs[rlepos].value; + int32_t end = start + src_1->runs[rlepos].length + 1; + int32_t xstart = src_2->runs[xrlepos].value; + int32_t xend = xstart + src_2->runs[xrlepos].length + 1; + while ((rlepos < src_1->n_runs) && (xrlepos < src_2->n_runs)) { + if (end <= xstart) { + ++rlepos; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + } else if (xend <= start) { + ++xrlepos; + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } else { // they overlap + const int32_t lateststart = start > xstart ? start : xstart; + int32_t earliestend; + if (end == xend) { // improbable + earliestend = end; + rlepos++; + xrlepos++; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } else if (end < xend) { + earliestend = end; + rlepos++; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + + } else { // end > xend + earliestend = xend; + xrlepos++; + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } + answer += earliestend - lateststart; + } + } + return answer; +} + +bool run_container_intersect(const run_container_t *src_1, + const run_container_t *src_2) { + const bool if1 = run_container_is_full(src_1); + const bool if2 = run_container_is_full(src_2); + if (if1 || if2) { + if (if1) { + return !run_container_empty(src_2); + } + if (if2) { + return !run_container_empty(src_1); + } + } + int32_t rlepos = 0; + int32_t xrlepos = 0; + int32_t start = src_1->runs[rlepos].value; + int32_t end = start + src_1->runs[rlepos].length + 1; + int32_t xstart = src_2->runs[xrlepos].value; + int32_t xend = xstart + src_2->runs[xrlepos].length + 1; + while ((rlepos < src_1->n_runs) && (xrlepos < src_2->n_runs)) { + if (end <= xstart) { + ++rlepos; + if (rlepos < src_1->n_runs) { + start = src_1->runs[rlepos].value; + end = start + src_1->runs[rlepos].length + 1; + } + } else if (xend <= start) { + ++xrlepos; + if (xrlepos < src_2->n_runs) { + xstart = src_2->runs[xrlepos].value; + xend = xstart + src_2->runs[xrlepos].length + 1; + } + } else { // they overlap + return true; + } + } + return false; +} + + +/* Compute the difference of src_1 and src_2 and write the result to + * dst. It is assumed that dst is distinct from both src_1 and src_2. */ +void run_container_andnot(const run_container_t *src_1, + const run_container_t *src_2, run_container_t *dst) { + // following Java implementation as of June 2016 + + if (dst->capacity < src_1->n_runs + src_2->n_runs) + run_container_grow(dst, src_1->n_runs + src_2->n_runs, false); + + dst->n_runs = 0; + + int rlepos1 = 0; + int rlepos2 = 0; + int32_t start = src_1->runs[rlepos1].value; + int32_t end = start + src_1->runs[rlepos1].length + 1; + int32_t start2 = src_2->runs[rlepos2].value; + int32_t end2 = start2 + src_2->runs[rlepos2].length + 1; + + while ((rlepos1 < src_1->n_runs) && (rlepos2 < src_2->n_runs)) { + if (end <= start2) { + // output the first run + dst->runs[dst->n_runs++] = + (rle16_t){.value = (uint16_t)start, + .length = (uint16_t)(end - start - 1)}; + rlepos1++; + if (rlepos1 < src_1->n_runs) { + start = src_1->runs[rlepos1].value; + end = start + src_1->runs[rlepos1].length + 1; + } + } else if (end2 <= start) { + // exit the second run + rlepos2++; + if (rlepos2 < src_2->n_runs) { + start2 = src_2->runs[rlepos2].value; + end2 = start2 + src_2->runs[rlepos2].length + 1; + } + } else { + if (start < start2) { + dst->runs[dst->n_runs++] = + (rle16_t){.value = (uint16_t)start, + .length = (uint16_t)(start2 - start - 1)}; + } + if (end2 < end) { + start = end2; + } else { + rlepos1++; + if (rlepos1 < src_1->n_runs) { + start = src_1->runs[rlepos1].value; + end = start + src_1->runs[rlepos1].length + 1; + } + } + } + } + if (rlepos1 < src_1->n_runs) { + dst->runs[dst->n_runs++] = (rle16_t){ + .value = (uint16_t)start, .length = (uint16_t)(end - start - 1)}; + rlepos1++; + if (rlepos1 < src_1->n_runs) { + memcpy(dst->runs + dst->n_runs, src_1->runs + rlepos1, + sizeof(rle16_t) * (src_1->n_runs - rlepos1)); + dst->n_runs += src_1->n_runs - rlepos1; + } + } +} + +int run_container_to_uint32_array(void *vout, const run_container_t *cont, + uint32_t base) { + int outpos = 0; + uint32_t *out = (uint32_t *)vout; + for (int i = 0; i < cont->n_runs; ++i) { + uint32_t run_start = base + cont->runs[i].value; + uint16_t le = cont->runs[i].length; + for (int j = 0; j <= le; ++j) { + uint32_t val = run_start + j; + memcpy(out + outpos, &val, + sizeof(uint32_t)); // should be compiled as a MOV on x64 + outpos++; + } + } + return outpos; +} + +/* + * Print this container using printf (useful for debugging). + */ +void run_container_printf(const run_container_t *cont) { + for (int i = 0; i < cont->n_runs; ++i) { + uint16_t run_start = cont->runs[i].value; + uint16_t le = cont->runs[i].length; + printf("[%d,%d]", run_start, run_start + le); + } +} + +/* + * Print this container using printf as a comma-separated list of 32-bit + * integers starting at base. + */ +void run_container_printf_as_uint32_array(const run_container_t *cont, + uint32_t base) { + if (cont->n_runs == 0) return; + { + uint32_t run_start = base + cont->runs[0].value; + uint16_t le = cont->runs[0].length; + printf("%u", run_start); + for (uint32_t j = 1; j <= le; ++j) printf(",%u", run_start + j); + } + for (int32_t i = 1; i < cont->n_runs; ++i) { + uint32_t run_start = base + cont->runs[i].value; + uint16_t le = cont->runs[i].length; + for (uint32_t j = 0; j <= le; ++j) printf(",%u", run_start + j); + } +} + +int32_t run_container_serialize(const run_container_t *container, char *buf) { + int32_t l, off; + + memcpy(buf, &container->n_runs, off = sizeof(container->n_runs)); + memcpy(&buf[off], &container->capacity, sizeof(container->capacity)); + off += sizeof(container->capacity); + + l = sizeof(rle16_t) * container->n_runs; + memcpy(&buf[off], container->runs, l); + return (off + l); +} + +int32_t run_container_write(const run_container_t *container, char *buf) { + memcpy(buf, &container->n_runs, sizeof(uint16_t)); + memcpy(buf + sizeof(uint16_t), container->runs, + container->n_runs * sizeof(rle16_t)); + return run_container_size_in_bytes(container); +} + +int32_t run_container_read(int32_t cardinality, run_container_t *container, + const char *buf) { + (void)cardinality; + memcpy(&container->n_runs, buf, sizeof(uint16_t)); + if (container->n_runs > container->capacity) + run_container_grow(container, container->n_runs, false); + if(container->n_runs > 0) { + memcpy(container->runs, buf + sizeof(uint16_t), + container->n_runs * sizeof(rle16_t)); + } + return run_container_size_in_bytes(container); +} + +uint32_t run_container_serialization_len(const run_container_t *container) { + return (sizeof(container->n_runs) + sizeof(container->capacity) + + sizeof(rle16_t) * container->n_runs); +} + +void *run_container_deserialize(const char *buf, size_t buf_len) { + run_container_t *ptr; + + if (buf_len < 8 /* n_runs + capacity */) + return (NULL); + else + buf_len -= 8; + + if ((ptr = (run_container_t *)malloc(sizeof(run_container_t))) != NULL) { + size_t len; + int32_t off; + + memcpy(&ptr->n_runs, buf, off = 4); + memcpy(&ptr->capacity, &buf[off], 4); + off += 4; + + len = sizeof(rle16_t) * ptr->n_runs; + + if (len != buf_len) { + free(ptr); + return (NULL); + } + + if ((ptr->runs = (rle16_t *)malloc(len)) == NULL) { + free(ptr); + return (NULL); + } + + memcpy(ptr->runs, &buf[off], len); + + /* Check if returned values are monotonically increasing */ + for (int32_t i = 0, j = 0; i < ptr->n_runs; i++) { + if (ptr->runs[i].value < j) { + free(ptr->runs); + free(ptr); + return (NULL); + } else + j = ptr->runs[i].value; + } + } + + return (ptr); +} + +bool run_container_iterate(const run_container_t *cont, uint32_t base, + roaring_iterator iterator, void *ptr) { + for (int i = 0; i < cont->n_runs; ++i) { + uint32_t run_start = base + cont->runs[i].value; + uint16_t le = cont->runs[i].length; + + for (int j = 0; j <= le; ++j) + if (!iterator(run_start + j, ptr)) return false; + } + return true; +} + +bool run_container_iterate64(const run_container_t *cont, uint32_t base, + roaring_iterator64 iterator, uint64_t high_bits, + void *ptr) { + for (int i = 0; i < cont->n_runs; ++i) { + uint32_t run_start = base + cont->runs[i].value; + uint16_t le = cont->runs[i].length; + + for (int j = 0; j <= le; ++j) + if (!iterator(high_bits | (uint64_t)(run_start + j), ptr)) + return false; + } + return true; +} + +bool run_container_is_subset(const run_container_t *container1, + const run_container_t *container2) { + int i1 = 0, i2 = 0; + while (i1 < container1->n_runs && i2 < container2->n_runs) { + int start1 = container1->runs[i1].value; + int stop1 = start1 + container1->runs[i1].length; + int start2 = container2->runs[i2].value; + int stop2 = start2 + container2->runs[i2].length; + if (start1 < start2) { + return false; + } else { // start1 >= start2 + if (stop1 < stop2) { + i1++; + } else if (stop1 == stop2) { + i1++; + i2++; + } else { // stop1 > stop2 + i2++; + } + } + } + if (i1 == container1->n_runs) { + return true; + } else { + return false; + } +} + +// TODO: write smart_append_exclusive version to match the overloaded 1 param +// Java version (or is it even used?) + +// follows the Java implementation closely +// length is the rle-value. Ie, run [10,12) uses a length value 1. +void run_container_smart_append_exclusive(run_container_t *src, + const uint16_t start, + const uint16_t length) { + int old_end; + rle16_t *last_run = src->n_runs ? src->runs + (src->n_runs - 1) : NULL; + rle16_t *appended_last_run = src->runs + src->n_runs; + + if (!src->n_runs || + (start > (old_end = last_run->value + last_run->length + 1))) { + *appended_last_run = (rle16_t){.value = start, .length = length}; + src->n_runs++; + return; + } + if (old_end == start) { + // we merge + last_run->length += (length + 1); + return; + } + int new_end = start + length + 1; + + if (start == last_run->value) { + // wipe out previous + if (new_end < old_end) { + *last_run = (rle16_t){.value = (uint16_t)new_end, + .length = (uint16_t)(old_end - new_end - 1)}; + return; + } else if (new_end > old_end) { + *last_run = (rle16_t){.value = (uint16_t)old_end, + .length = (uint16_t)(new_end - old_end - 1)}; + return; + } else { + src->n_runs--; + return; + } + } + last_run->length = start - last_run->value - 1; + if (new_end < old_end) { + *appended_last_run = + (rle16_t){.value = (uint16_t)new_end, + .length = (uint16_t)(old_end - new_end - 1)}; + src->n_runs++; + } else if (new_end > old_end) { + *appended_last_run = + (rle16_t){.value = (uint16_t)old_end, + .length = (uint16_t)(new_end - old_end - 1)}; + src->n_runs++; + } +} + +bool run_container_select(const run_container_t *container, + uint32_t *start_rank, uint32_t rank, + uint32_t *element) { + for (int i = 0; i < container->n_runs; i++) { + uint16_t length = container->runs[i].length; + if (rank <= *start_rank + length) { + uint16_t value = container->runs[i].value; + *element = value + rank - (*start_rank); + return true; + } else + *start_rank += length + 1; + } + return false; +} + +int run_container_rank(const run_container_t *container, uint16_t x) { + int sum = 0; + uint32_t x32 = x; + for (int i = 0; i < container->n_runs; i++) { + uint32_t startpoint = container->runs[i].value; + uint32_t length = container->runs[i].length; + uint32_t endpoint = length + startpoint; + if (x <= endpoint) { + if (x < startpoint) break; + return sum + (x32 - startpoint) + 1; + } else { + sum += length + 1; + } + } + return sum; +} +/* end file src/containers/run.c */ +/* begin file src/roaring.c */ +#include +#include +#include +#include +#include +#include + +static inline bool is_cow(const roaring_bitmap_t *r) { + return r->high_low_container.flags & ROARING_FLAG_COW; +} +static inline bool is_frozen(const roaring_bitmap_t *r) { + return r->high_low_container.flags & ROARING_FLAG_FROZEN; +} + +// this is like roaring_bitmap_add, but it populates pointer arguments in such a +// way +// that we can recover the container touched, which, in turn can be used to +// accelerate some functions (when you repeatedly need to add to the same +// container) +static inline void *containerptr_roaring_bitmap_add(roaring_bitmap_t *r, + uint32_t val, + uint8_t *typecode, + int *index) { + uint16_t hb = val >> 16; + const int i = ra_get_index(&r->high_low_container, hb); + if (i >= 0) { + ra_unshare_container_at_index(&r->high_low_container, i); + void *container = + ra_get_container_at_index(&r->high_low_container, i, typecode); + uint8_t newtypecode = *typecode; + void *container2 = + container_add(container, val & 0xFFFF, *typecode, &newtypecode); + *index = i; + if (container2 != container) { + container_free(container, *typecode); + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + *typecode = newtypecode; + return container2; + } else { + return container; + } + } else { + array_container_t *newac = array_container_create(); + void *container = container_add(newac, val & 0xFFFF, + ARRAY_CONTAINER_TYPE_CODE, typecode); + // we could just assume that it stays an array container + ra_insert_new_key_value_at(&r->high_low_container, -i - 1, hb, + container, *typecode); + *index = -i - 1; + return container; + } +} + +roaring_bitmap_t *roaring_bitmap_create(void) { + roaring_bitmap_t *ans = + (roaring_bitmap_t *)malloc(sizeof(roaring_bitmap_t)); + if (!ans) { + return NULL; + } + ra_init(&ans->high_low_container); + return ans; +} + +roaring_bitmap_t *roaring_bitmap_create_with_capacity(uint32_t cap) { + roaring_bitmap_t *ans = + (roaring_bitmap_t *)malloc(sizeof(roaring_bitmap_t)); + if (!ans) { + return NULL; + } + bool is_ok = ra_init_with_capacity(&ans->high_low_container, cap); + if (!is_ok) { + free(ans); + return NULL; + } + return ans; +} + +void roaring_bitmap_add_many(roaring_bitmap_t *r, size_t n_args, + const uint32_t *vals) { + void *container = NULL; // hold value of last container touched + uint8_t typecode = 0; // typecode of last container touched + uint32_t prev = 0; // previous valued inserted + size_t i = 0; // index of value + int containerindex = 0; + if (n_args == 0) return; + uint32_t val; + memcpy(&val, vals + i, sizeof(val)); + container = + containerptr_roaring_bitmap_add(r, val, &typecode, &containerindex); + prev = val; + i++; + for (; i < n_args; i++) { + memcpy(&val, vals + i, sizeof(val)); + if (((prev ^ val) >> 16) == + 0) { // no need to seek the container, it is at hand + // because we already have the container at hand, we can do the + // insertion + // automatically, bypassing the roaring_bitmap_add call + uint8_t newtypecode = typecode; + void *container2 = + container_add(container, val & 0xFFFF, typecode, &newtypecode); + if (container2 != container) { // rare instance when we need to + // change the container type + container_free(container, typecode); + ra_set_container_at_index(&r->high_low_container, + containerindex, container2, + newtypecode); + typecode = newtypecode; + container = container2; + } + } else { + container = containerptr_roaring_bitmap_add(r, val, &typecode, + &containerindex); + } + prev = val; + } +} + +roaring_bitmap_t *roaring_bitmap_of_ptr(size_t n_args, const uint32_t *vals) { + roaring_bitmap_t *answer = roaring_bitmap_create(); + roaring_bitmap_add_many(answer, n_args, vals); + return answer; +} + +roaring_bitmap_t *roaring_bitmap_of(size_t n_args, ...) { + // todo: could be greatly optimized but we do not expect this call to ever + // include long lists + roaring_bitmap_t *answer = roaring_bitmap_create(); + va_list ap; + va_start(ap, n_args); + for (size_t i = 1; i <= n_args; i++) { + uint32_t val = va_arg(ap, uint32_t); + roaring_bitmap_add(answer, val); + } + va_end(ap); + return answer; +} + +static inline uint32_t minimum_uint32(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +static inline uint64_t minimum_uint64(uint64_t a, uint64_t b) { + return (a < b) ? a : b; +} + +roaring_bitmap_t *roaring_bitmap_from_range(uint64_t min, uint64_t max, + uint32_t step) { + if(max >= UINT64_C(0x100000000)) { + max = UINT64_C(0x100000000); + } + if (step == 0) return NULL; + if (max <= min) return NULL; + roaring_bitmap_t *answer = roaring_bitmap_create(); + if (step >= (1 << 16)) { + for (uint32_t value = (uint32_t)min; value < max; value += step) { + roaring_bitmap_add(answer, value); + } + return answer; + } + uint64_t min_tmp = min; + do { + uint32_t key = (uint32_t)min_tmp >> 16; + uint32_t container_min = min_tmp & 0xFFFF; + uint32_t container_max = (uint32_t)minimum_uint64(max - (key << 16), 1 << 16); + uint8_t type; + void *container = container_from_range(&type, container_min, + container_max, (uint16_t)step); + ra_append(&answer->high_low_container, key, container, type); + uint32_t gap = container_max - container_min + step - 1; + min_tmp += gap - (gap % step); + } while (min_tmp < max); + // cardinality of bitmap will be ((uint64_t) max - min + step - 1 ) / step + return answer; +} + +void roaring_bitmap_add_range_closed(roaring_bitmap_t *ra, uint32_t min, uint32_t max) { + if (min > max) { + return; + } + + uint32_t min_key = min >> 16; + uint32_t max_key = max >> 16; + + int32_t num_required_containers = max_key - min_key + 1; + int32_t suffix_length = count_greater(ra->high_low_container.keys, + ra->high_low_container.size, + max_key); + int32_t prefix_length = count_less(ra->high_low_container.keys, + ra->high_low_container.size - suffix_length, + min_key); + int32_t common_length = ra->high_low_container.size - prefix_length - suffix_length; + + if (num_required_containers > common_length) { + ra_shift_tail(&ra->high_low_container, suffix_length, + num_required_containers - common_length); + } + + int32_t src = prefix_length + common_length - 1; + int32_t dst = ra->high_low_container.size - suffix_length - 1; + for (uint32_t key = max_key; key != min_key-1; key--) { // beware of min_key==0 + uint32_t container_min = (min_key == key) ? (min & 0xffff) : 0; + uint32_t container_max = (max_key == key) ? (max & 0xffff) : 0xffff; + void* new_container; + uint8_t new_type; + + if (src >= 0 && ra->high_low_container.keys[src] == key) { + ra_unshare_container_at_index(&ra->high_low_container, src); + new_container = container_add_range(ra->high_low_container.containers[src], + ra->high_low_container.typecodes[src], + container_min, container_max, &new_type); + if (new_container != ra->high_low_container.containers[src]) { + container_free(ra->high_low_container.containers[src], + ra->high_low_container.typecodes[src]); + } + src--; + } else { + new_container = container_from_range(&new_type, container_min, + container_max+1, 1); + } + ra_replace_key_and_container_at_index(&ra->high_low_container, dst, + key, new_container, new_type); + dst--; + } +} + +void roaring_bitmap_remove_range_closed(roaring_bitmap_t *ra, uint32_t min, uint32_t max) { + if (min > max) { + return; + } + + uint32_t min_key = min >> 16; + uint32_t max_key = max >> 16; + + int32_t src = count_less(ra->high_low_container.keys, ra->high_low_container.size, min_key); + int32_t dst = src; + while (src < ra->high_low_container.size && ra->high_low_container.keys[src] <= max_key) { + uint32_t container_min = (min_key == ra->high_low_container.keys[src]) ? (min & 0xffff) : 0; + uint32_t container_max = (max_key == ra->high_low_container.keys[src]) ? (max & 0xffff) : 0xffff; + ra_unshare_container_at_index(&ra->high_low_container, src); + void *new_container; + uint8_t new_type; + new_container = container_remove_range(ra->high_low_container.containers[src], + ra->high_low_container.typecodes[src], + container_min, container_max, + &new_type); + if (new_container != ra->high_low_container.containers[src]) { + container_free(ra->high_low_container.containers[src], + ra->high_low_container.typecodes[src]); + } + if (new_container) { + ra_replace_key_and_container_at_index(&ra->high_low_container, dst, + ra->high_low_container.keys[src], + new_container, new_type); + dst++; + } + src++; + } + if (src > dst) { + ra_shift_tail(&ra->high_low_container, ra->high_low_container.size - src, dst - src); + } +} + +void roaring_bitmap_printf(const roaring_bitmap_t *ra) { + printf("{"); + for (int i = 0; i < ra->high_low_container.size; ++i) { + container_printf_as_uint32_array( + ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i], + ((uint32_t)ra->high_low_container.keys[i]) << 16); + if (i + 1 < ra->high_low_container.size) printf(","); + } + printf("}"); +} + +void roaring_bitmap_printf_describe(const roaring_bitmap_t *ra) { + printf("{"); + for (int i = 0; i < ra->high_low_container.size; ++i) { + printf("%d: %s (%d)", ra->high_low_container.keys[i], + get_full_container_name(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]), + container_get_cardinality(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i])); + if (ra->high_low_container.typecodes[i] == SHARED_CONTAINER_TYPE_CODE) { + printf( + "(shared count = %" PRIu32 " )", + ((shared_container_t *)(ra->high_low_container.containers[i])) + ->counter); + } + + if (i + 1 < ra->high_low_container.size) printf(", "); + } + printf("}"); +} + +typedef struct min_max_sum_s { + uint32_t min; + uint32_t max; + uint64_t sum; +} min_max_sum_t; + +static bool min_max_sum_fnc(uint32_t value, void *param) { + min_max_sum_t *mms = (min_max_sum_t *)param; + if (value > mms->max) mms->max = value; + if (value < mms->min) mms->min = value; + mms->sum += value; + return true; // we always process all data points +} + +/** +* (For advanced users.) +* Collect statistics about the bitmap +*/ +void roaring_bitmap_statistics(const roaring_bitmap_t *ra, + roaring_statistics_t *stat) { + memset(stat, 0, sizeof(*stat)); + stat->n_containers = ra->high_low_container.size; + stat->cardinality = roaring_bitmap_get_cardinality(ra); + min_max_sum_t mms; + mms.min = UINT32_C(0xFFFFFFFF); + mms.max = UINT32_C(0); + mms.sum = 0; + roaring_iterate(ra, &min_max_sum_fnc, &mms); + stat->min_value = mms.min; + stat->max_value = mms.max; + stat->sum_value = mms.sum; + + for (int i = 0; i < ra->high_low_container.size; ++i) { + uint8_t truetype = + get_container_type(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]); + uint32_t card = + container_get_cardinality(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]); + uint32_t sbytes = + container_size_in_bytes(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]); + switch (truetype) { + case BITSET_CONTAINER_TYPE_CODE: + stat->n_bitset_containers++; + stat->n_values_bitset_containers += card; + stat->n_bytes_bitset_containers += sbytes; + break; + case ARRAY_CONTAINER_TYPE_CODE: + stat->n_array_containers++; + stat->n_values_array_containers += card; + stat->n_bytes_array_containers += sbytes; + break; + case RUN_CONTAINER_TYPE_CODE: + stat->n_run_containers++; + stat->n_values_run_containers += card; + stat->n_bytes_run_containers += sbytes; + break; + default: + assert(false); + __builtin_unreachable(); + } + } +} + +roaring_bitmap_t *roaring_bitmap_copy(const roaring_bitmap_t *r) { + roaring_bitmap_t *ans = + (roaring_bitmap_t *)malloc(sizeof(roaring_bitmap_t)); + if (!ans) { + return NULL; + } + bool is_ok = ra_copy(&r->high_low_container, &ans->high_low_container, + is_cow(r)); + if (!is_ok) { + free(ans); + return NULL; + } + roaring_bitmap_set_copy_on_write(ans, is_cow(r)); + return ans; +} + +bool roaring_bitmap_overwrite(roaring_bitmap_t *dest, + const roaring_bitmap_t *src) { + return ra_overwrite(&src->high_low_container, &dest->high_low_container, + is_cow(src)); +} + +void roaring_bitmap_free(const roaring_bitmap_t *r) { + if (!is_frozen(r)) { + ra_clear((roaring_array_t*)&r->high_low_container); + } + free((roaring_bitmap_t*)r); +} + +void roaring_bitmap_clear(roaring_bitmap_t *r) { + ra_reset(&r->high_low_container); +} + +void roaring_bitmap_add(roaring_bitmap_t *r, uint32_t val) { + const uint16_t hb = val >> 16; + const int i = ra_get_index(&r->high_low_container, hb); + uint8_t typecode; + if (i >= 0) { + ra_unshare_container_at_index(&r->high_low_container, i); + void *container = + ra_get_container_at_index(&r->high_low_container, i, &typecode); + uint8_t newtypecode = typecode; + void *container2 = + container_add(container, val & 0xFFFF, typecode, &newtypecode); + if (container2 != container) { + container_free(container, typecode); + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + } + } else { + array_container_t *newac = array_container_create(); + void *container = container_add(newac, val & 0xFFFF, + ARRAY_CONTAINER_TYPE_CODE, &typecode); + // we could just assume that it stays an array container + ra_insert_new_key_value_at(&r->high_low_container, -i - 1, hb, + container, typecode); + } +} + +bool roaring_bitmap_add_checked(roaring_bitmap_t *r, uint32_t val) { + const uint16_t hb = val >> 16; + const int i = ra_get_index(&r->high_low_container, hb); + uint8_t typecode; + bool result = false; + if (i >= 0) { + ra_unshare_container_at_index(&r->high_low_container, i); + void *container = + ra_get_container_at_index(&r->high_low_container, i, &typecode); + + const int oldCardinality = + container_get_cardinality(container, typecode); + + uint8_t newtypecode = typecode; + void *container2 = + container_add(container, val & 0xFFFF, typecode, &newtypecode); + if (container2 != container) { + container_free(container, typecode); + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + result = true; + } else { + const int newCardinality = + container_get_cardinality(container, newtypecode); + + result = oldCardinality != newCardinality; + } + } else { + array_container_t *newac = array_container_create(); + void *container = container_add(newac, val & 0xFFFF, + ARRAY_CONTAINER_TYPE_CODE, &typecode); + // we could just assume that it stays an array container + ra_insert_new_key_value_at(&r->high_low_container, -i - 1, hb, + container, typecode); + result = true; + } + + return result; +} + +void roaring_bitmap_remove(roaring_bitmap_t *r, uint32_t val) { + const uint16_t hb = val >> 16; + const int i = ra_get_index(&r->high_low_container, hb); + uint8_t typecode; + if (i >= 0) { + ra_unshare_container_at_index(&r->high_low_container, i); + void *container = + ra_get_container_at_index(&r->high_low_container, i, &typecode); + uint8_t newtypecode = typecode; + void *container2 = + container_remove(container, val & 0xFFFF, typecode, &newtypecode); + if (container2 != container) { + container_free(container, typecode); + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + } + if (container_get_cardinality(container2, newtypecode) != 0) { + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + } else { + ra_remove_at_index_and_free(&r->high_low_container, i); + } + } +} + +bool roaring_bitmap_remove_checked(roaring_bitmap_t *r, uint32_t val) { + const uint16_t hb = val >> 16; + const int i = ra_get_index(&r->high_low_container, hb); + uint8_t typecode; + bool result = false; + if (i >= 0) { + ra_unshare_container_at_index(&r->high_low_container, i); + void *container = + ra_get_container_at_index(&r->high_low_container, i, &typecode); + + const int oldCardinality = + container_get_cardinality(container, typecode); + + uint8_t newtypecode = typecode; + void *container2 = + container_remove(container, val & 0xFFFF, typecode, &newtypecode); + if (container2 != container) { + container_free(container, typecode); + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + } + + const int newCardinality = + container_get_cardinality(container2, newtypecode); + + if (newCardinality != 0) { + ra_set_container_at_index(&r->high_low_container, i, container2, + newtypecode); + } else { + ra_remove_at_index_and_free(&r->high_low_container, i); + } + + result = oldCardinality != newCardinality; + } + return result; +} + +void roaring_bitmap_remove_many(roaring_bitmap_t *r, size_t n_args, + const uint32_t *vals) { + if (n_args == 0 || r->high_low_container.size == 0) { + return; + } + int32_t pos = -1; // position of the container used in the previous iteration + for (size_t i = 0; i < n_args; i++) { + uint16_t key = (uint16_t)(vals[i] >> 16); + if (pos < 0 || key != r->high_low_container.keys[pos]) { + pos = ra_get_index(&r->high_low_container, key); + } + if (pos >= 0) { + uint8_t new_typecode; + void *new_container; + new_container = container_remove(r->high_low_container.containers[pos], + vals[i] & 0xffff, + r->high_low_container.typecodes[pos], + &new_typecode); + if (new_container != r->high_low_container.containers[pos]) { + container_free(r->high_low_container.containers[pos], + r->high_low_container.typecodes[pos]); + ra_replace_key_and_container_at_index(&r->high_low_container, + pos, key, new_container, + new_typecode); + } + if (!container_nonzero_cardinality(new_container, new_typecode)) { + container_free(new_container, new_typecode); + ra_remove_at_index(&r->high_low_container, pos); + pos = -1; + } + } + } +} + +// there should be some SIMD optimizations possible here +roaring_bitmap_t *roaring_bitmap_and(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + uint32_t neededcap = length1 > length2 ? length2 : length1; + roaring_bitmap_t *answer = roaring_bitmap_create_with_capacity(neededcap); + roaring_bitmap_set_copy_on_write(answer, is_cow(x1) && is_cow(x2)); + + int pos1 = 0, pos2 = 0; + + while (pos1 < length1 && pos2 < length2) { + const uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + const uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + if (s1 == s2) { + uint8_t container_type_1, container_type_2; + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = container_and(c1, container_type_1, c2, container_type_2, + &container_result_type); + if (container_nonzero_cardinality(c, container_result_type)) { + ra_append(&answer->high_low_container, s1, c, + container_result_type); + } else { + container_free( + c, container_result_type); // otherwise:memory leak! + } + ++pos1; + ++pos2; + } else if (s1 < s2) { // s1 < s2 + pos1 = ra_advance_until(&x1->high_low_container, s2, pos1); + } else { // s1 > s2 + pos2 = ra_advance_until(&x2->high_low_container, s1, pos2); + } + } + return answer; +} + +/** + * Compute the union of 'number' bitmaps. + */ +roaring_bitmap_t *roaring_bitmap_or_many(size_t number, + const roaring_bitmap_t **x) { + if (number == 0) { + return roaring_bitmap_create(); + } + if (number == 1) { + return roaring_bitmap_copy(x[0]); + } + roaring_bitmap_t *answer = + roaring_bitmap_lazy_or(x[0], x[1], LAZY_OR_BITSET_CONVERSION); + for (size_t i = 2; i < number; i++) { + roaring_bitmap_lazy_or_inplace(answer, x[i], LAZY_OR_BITSET_CONVERSION); + } + roaring_bitmap_repair_after_lazy(answer); + return answer; +} + +/** + * Compute the xor of 'number' bitmaps. + */ +roaring_bitmap_t *roaring_bitmap_xor_many(size_t number, + const roaring_bitmap_t **x) { + if (number == 0) { + return roaring_bitmap_create(); + } + if (number == 1) { + return roaring_bitmap_copy(x[0]); + } + roaring_bitmap_t *answer = roaring_bitmap_lazy_xor(x[0], x[1]); + for (size_t i = 2; i < number; i++) { + roaring_bitmap_lazy_xor_inplace(answer, x[i]); + } + roaring_bitmap_repair_after_lazy(answer); + return answer; +} + +// inplace and (modifies its first argument). +void roaring_bitmap_and_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + if (x1 == x2) return; + int pos1 = 0, pos2 = 0, intersection_size = 0; + const int length1 = ra_get_size(&x1->high_low_container); + const int length2 = ra_get_size(&x2->high_low_container); + + // any skipped-over or newly emptied containers in x1 + // have to be freed. + while (pos1 < length1 && pos2 < length2) { + const uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + const uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + if (s1 == s2) { + uint8_t typecode1, typecode2, typecode_result; + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &typecode1); + c1 = get_writable_copy_if_shared(c1, &typecode1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &typecode2); + void *c = + container_iand(c1, typecode1, c2, typecode2, &typecode_result); + if (c != c1) { // in this instance a new container was created, and + // we need to free the old one + container_free(c1, typecode1); + } + if (container_nonzero_cardinality(c, typecode_result)) { + ra_replace_key_and_container_at_index(&x1->high_low_container, + intersection_size, s1, c, + typecode_result); + intersection_size++; + } else { + container_free(c, typecode_result); + } + ++pos1; + ++pos2; + } else if (s1 < s2) { + pos1 = ra_advance_until_freeing(&x1->high_low_container, s2, pos1); + } else { // s1 > s2 + pos2 = ra_advance_until(&x2->high_low_container, s1, pos2); + } + } + + // if we ended early because x2 ran out, then all remaining in x1 should be + // freed + while (pos1 < length1) { + container_free(x1->high_low_container.containers[pos1], + x1->high_low_container.typecodes[pos1]); + ++pos1; + } + + // all containers after this have either been copied or freed + ra_downsize(&x1->high_low_container, intersection_size); +} + +roaring_bitmap_t *roaring_bitmap_or(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + if (0 == length1) { + return roaring_bitmap_copy(x2); + } + if (0 == length2) { + return roaring_bitmap_copy(x1); + } + roaring_bitmap_t *answer = + roaring_bitmap_create_with_capacity(length1 + length2); + roaring_bitmap_set_copy_on_write(answer, is_cow(x1) && is_cow(x2)); + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = container_or(c1, container_type_1, c2, container_type_2, + &container_result_type); + // since we assume that the initial containers are non-empty, the + // result here + // can only be non-empty + ra_append(&answer->high_low_container, s1, c, + container_result_type); + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + // c1 = container_clone(c1, container_type_1); + c1 = + get_copy_of_container(c1, &container_type_1, is_cow(x1)); + if (is_cow(x1)) { + ra_set_container_at_index(&x1->high_low_container, pos1, c1, + container_type_1); + } + ra_append(&answer->high_low_container, s1, c1, container_type_1); + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + // c2 = container_clone(c2, container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + ra_append(&answer->high_low_container, s2, c2, container_type_2); + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&answer->high_low_container, + &x2->high_low_container, pos2, length2, + is_cow(x2)); + } else if (pos2 == length2) { + ra_append_copy_range(&answer->high_low_container, + &x1->high_low_container, pos1, length1, + is_cow(x1)); + } + return answer; +} + +// inplace or (modifies its first argument). +void roaring_bitmap_or_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + int length1 = x1->high_low_container.size; + const int length2 = x2->high_low_container.size; + + if (0 == length2) return; + + if (0 == length1) { + roaring_bitmap_overwrite(x1, x2); + return; + } + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + if (!container_is_full(c1, container_type_1)) { + c1 = get_writable_copy_if_shared(c1, &container_type_1); + + void *c2 = ra_get_container_at_index(&x2->high_low_container, + pos2, &container_type_2); + void *c = + container_ior(c1, container_type_1, c2, container_type_2, + &container_result_type); + if (c != + c1) { // in this instance a new container was created, and + // we need to free the old one + container_free(c1, container_type_1); + } + + ra_set_container_at_index(&x1->high_low_container, pos1, c, + container_result_type); + } + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + + // void *c2_clone = container_clone(c2, container_type_2); + ra_insert_new_key_value_at(&x1->high_low_container, pos1, s2, c2, + container_type_2); + pos1++; + length1++; + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&x1->high_low_container, &x2->high_low_container, + pos2, length2, is_cow(x2)); + } +} + +roaring_bitmap_t *roaring_bitmap_xor(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + if (0 == length1) { + return roaring_bitmap_copy(x2); + } + if (0 == length2) { + return roaring_bitmap_copy(x1); + } + roaring_bitmap_t *answer = + roaring_bitmap_create_with_capacity(length1 + length2); + roaring_bitmap_set_copy_on_write(answer, is_cow(x1) && is_cow(x2)); + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = container_xor(c1, container_type_1, c2, container_type_2, + &container_result_type); + + if (container_nonzero_cardinality(c, container_result_type)) { + ra_append(&answer->high_low_container, s1, c, + container_result_type); + } else { + container_free(c, container_result_type); + } + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + c1 = + get_copy_of_container(c1, &container_type_1, is_cow(x1)); + if (is_cow(x1)) { + ra_set_container_at_index(&x1->high_low_container, pos1, c1, + container_type_1); + } + ra_append(&answer->high_low_container, s1, c1, container_type_1); + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + ra_append(&answer->high_low_container, s2, c2, container_type_2); + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&answer->high_low_container, + &x2->high_low_container, pos2, length2, + is_cow(x2)); + } else if (pos2 == length2) { + ra_append_copy_range(&answer->high_low_container, + &x1->high_low_container, pos1, length1, + is_cow(x1)); + } + return answer; +} + +// inplace xor (modifies its first argument). + +void roaring_bitmap_xor_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + assert(x1 != x2); + uint8_t container_result_type = 0; + int length1 = x1->high_low_container.size; + const int length2 = x2->high_low_container.size; + + if (0 == length2) return; + + if (0 == length1) { + roaring_bitmap_overwrite(x1, x2); + return; + } + + // XOR can have new containers inserted from x2, but can also + // lose containers when x1 and x2 are nonempty and identical. + + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + c1 = get_writable_copy_if_shared(c1, &container_type_1); + + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = container_ixor(c1, container_type_1, c2, container_type_2, + &container_result_type); + + if (container_nonzero_cardinality(c, container_result_type)) { + ra_set_container_at_index(&x1->high_low_container, pos1, c, + container_result_type); + ++pos1; + } else { + container_free(c, container_result_type); + ra_remove_at_index(&x1->high_low_container, pos1); + --length1; + } + + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + + ra_insert_new_key_value_at(&x1->high_low_container, pos1, s2, c2, + container_type_2); + pos1++; + length1++; + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&x1->high_low_container, &x2->high_low_container, + pos2, length2, is_cow(x2)); + } +} + +roaring_bitmap_t *roaring_bitmap_andnot(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + if (0 == length1) { + roaring_bitmap_t *empty_bitmap = roaring_bitmap_create(); + roaring_bitmap_set_copy_on_write(empty_bitmap, is_cow(x1) && is_cow(x2)); + return empty_bitmap; + } + if (0 == length2) { + return roaring_bitmap_copy(x1); + } + roaring_bitmap_t *answer = roaring_bitmap_create_with_capacity(length1); + roaring_bitmap_set_copy_on_write(answer, is_cow(x1) && is_cow(x2)); + + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = 0; + uint16_t s2 = 0; + while (true) { + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = + container_andnot(c1, container_type_1, c2, container_type_2, + &container_result_type); + + if (container_nonzero_cardinality(c, container_result_type)) { + ra_append(&answer->high_low_container, s1, c, + container_result_type); + } else { + container_free(c, container_result_type); + } + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + } else if (s1 < s2) { // s1 < s2 + const int next_pos1 = + ra_advance_until(&x1->high_low_container, s2, pos1); + ra_append_copy_range(&answer->high_low_container, + &x1->high_low_container, pos1, next_pos1, + is_cow(x1)); + // TODO : perhaps some of the copy_on_write should be based on + // answer rather than x1 (more stringent?). Many similar cases + pos1 = next_pos1; + if (pos1 == length1) break; + } else { // s1 > s2 + pos2 = ra_advance_until(&x2->high_low_container, s1, pos2); + if (pos2 == length2) break; + } + } + if (pos2 == length2) { + ra_append_copy_range(&answer->high_low_container, + &x1->high_low_container, pos1, length1, + is_cow(x1)); + } + return answer; +} + +// inplace andnot (modifies its first argument). + +void roaring_bitmap_andnot_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + assert(x1 != x2); + + uint8_t container_result_type = 0; + int length1 = x1->high_low_container.size; + const int length2 = x2->high_low_container.size; + int intersection_size = 0; + + if (0 == length2) return; + + if (0 == length1) { + roaring_bitmap_clear(x1); + return; + } + + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + c1 = get_writable_copy_if_shared(c1, &container_type_1); + + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = + container_iandnot(c1, container_type_1, c2, container_type_2, + &container_result_type); + + if (container_nonzero_cardinality(c, container_result_type)) { + ra_replace_key_and_container_at_index(&x1->high_low_container, + intersection_size++, s1, + c, container_result_type); + } else { + container_free(c, container_result_type); + } + + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + if (pos1 != intersection_size) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, + pos1, &container_type_1); + + ra_replace_key_and_container_at_index(&x1->high_low_container, + intersection_size, s1, c1, + container_type_1); + } + intersection_size++; + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + pos2 = ra_advance_until(&x2->high_low_container, s1, pos2); + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + + if (pos1 < length1) { + // all containers between intersection_size and + // pos1 are junk. However, they have either been moved + // (thus still referenced) or involved in an iandnot + // that will clean up all containers that could not be reused. + // Thus we should not free the junk containers between + // intersection_size and pos1. + if (pos1 > intersection_size) { + // left slide of remaining items + ra_copy_range(&x1->high_low_container, pos1, length1, + intersection_size); + } + // else current placement is fine + intersection_size += (length1 - pos1); + } + ra_downsize(&x1->high_low_container, intersection_size); +} + +uint64_t roaring_bitmap_get_cardinality(const roaring_bitmap_t *ra) { + uint64_t card = 0; + for (int i = 0; i < ra->high_low_container.size; ++i) + card += container_get_cardinality(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]); + return card; +} + +uint64_t roaring_bitmap_range_cardinality(const roaring_bitmap_t *ra, + uint64_t range_start, + uint64_t range_end) { + if (range_end > UINT32_MAX) { + range_end = UINT32_MAX + UINT64_C(1); + } + if (range_start >= range_end) { + return 0; + } + range_end--; // make range_end inclusive + // now we have: 0 <= range_start <= range_end <= UINT32_MAX + + uint16_t minhb = range_start >> 16; + uint16_t maxhb = range_end >> 16; + + uint64_t card = 0; + + int i = ra_get_index(&ra->high_low_container, minhb); + if (i >= 0) { + if (minhb == maxhb) { + card += container_rank(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i], + range_end & 0xffff); + } else { + card += container_get_cardinality(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]); + } + if ((range_start & 0xffff) != 0) { + card -= container_rank(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i], + (range_start & 0xffff) - 1); + } + i++; + } else { + i = -i - 1; + } + + for (; i < ra->high_low_container.size; i++) { + uint16_t key = ra->high_low_container.keys[i]; + if (key < maxhb) { + card += container_get_cardinality(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i]); + } else if (key == maxhb) { + card += container_rank(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i], + range_end & 0xffff); + break; + } else { + break; + } + } + + return card; +} + + +bool roaring_bitmap_is_empty(const roaring_bitmap_t *ra) { + return ra->high_low_container.size == 0; +} + +void roaring_bitmap_to_uint32_array(const roaring_bitmap_t *ra, uint32_t *ans) { + ra_to_uint32_array(&ra->high_low_container, ans); +} + +bool roaring_bitmap_range_uint32_array(const roaring_bitmap_t *ra, size_t offset, size_t limit, uint32_t *ans) { + return ra_range_uint32_array(&ra->high_low_container, offset, limit, ans); +} + +/** convert array and bitmap containers to run containers when it is more + * efficient; + * also convert from run containers when more space efficient. Returns + * true if the result has at least one run container. +*/ +bool roaring_bitmap_run_optimize(roaring_bitmap_t *r) { + bool answer = false; + for (int i = 0; i < r->high_low_container.size; i++) { + uint8_t typecode_original, typecode_after; + ra_unshare_container_at_index( + &r->high_low_container, i); // TODO: this introduces extra cloning! + void *c = ra_get_container_at_index(&r->high_low_container, i, + &typecode_original); + void *c1 = convert_run_optimize(c, typecode_original, &typecode_after); + if (typecode_after == RUN_CONTAINER_TYPE_CODE) answer = true; + ra_set_container_at_index(&r->high_low_container, i, c1, + typecode_after); + } + return answer; +} + +size_t roaring_bitmap_shrink_to_fit(roaring_bitmap_t *r) { + size_t answer = 0; + for (int i = 0; i < r->high_low_container.size; i++) { + uint8_t typecode_original; + void *c = ra_get_container_at_index(&r->high_low_container, i, + &typecode_original); + answer += container_shrink_to_fit(c, typecode_original); + } + answer += ra_shrink_to_fit(&r->high_low_container); + return answer; +} + +/** + * Remove run-length encoding even when it is more space efficient + * return whether a change was applied + */ +bool roaring_bitmap_remove_run_compression(roaring_bitmap_t *r) { + bool answer = false; + for (int i = 0; i < r->high_low_container.size; i++) { + uint8_t typecode_original, typecode_after; + void *c = ra_get_container_at_index(&r->high_low_container, i, + &typecode_original); + if (get_container_type(c, typecode_original) == + RUN_CONTAINER_TYPE_CODE) { + answer = true; + if (typecode_original == SHARED_CONTAINER_TYPE_CODE) { + run_container_t *truec = + (run_container_t *)((shared_container_t *)c)->container; + int32_t card = run_container_cardinality(truec); + void *c1 = convert_to_bitset_or_array_container( + truec, card, &typecode_after); + shared_container_free((shared_container_t *)c);// will free the run container as needed + ra_set_container_at_index(&r->high_low_container, i, c1, + typecode_after); + + } else { + int32_t card = run_container_cardinality((run_container_t *)c); + void *c1 = convert_to_bitset_or_array_container( + (run_container_t *)c, card, &typecode_after); + run_container_free((run_container_t *)c); + ra_set_container_at_index(&r->high_low_container, i, c1, + typecode_after); + } + } + } + return answer; +} + +size_t roaring_bitmap_serialize(const roaring_bitmap_t *ra, char *buf) { + size_t portablesize = roaring_bitmap_portable_size_in_bytes(ra); + uint64_t cardinality = roaring_bitmap_get_cardinality(ra); + uint64_t sizeasarray = cardinality * sizeof(uint32_t) + sizeof(uint32_t); + if (portablesize < sizeasarray) { + buf[0] = SERIALIZATION_CONTAINER; + return roaring_bitmap_portable_serialize(ra, buf + 1) + 1; + } else { + buf[0] = SERIALIZATION_ARRAY_UINT32; + memcpy(buf + 1, &cardinality, sizeof(uint32_t)); + roaring_bitmap_to_uint32_array( + ra, (uint32_t *)(buf + 1 + sizeof(uint32_t))); + return 1 + (size_t)sizeasarray; + } +} + +size_t roaring_bitmap_size_in_bytes(const roaring_bitmap_t *ra) { + size_t portablesize = roaring_bitmap_portable_size_in_bytes(ra); + uint64_t sizeasarray = roaring_bitmap_get_cardinality(ra) * sizeof(uint32_t) + + sizeof(uint32_t); + return portablesize < sizeasarray ? portablesize + 1 : (size_t)sizeasarray + 1; +} + +size_t roaring_bitmap_portable_size_in_bytes(const roaring_bitmap_t *ra) { + return ra_portable_size_in_bytes(&ra->high_low_container); +} + + +roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size_t maxbytes) { + roaring_bitmap_t *ans = + (roaring_bitmap_t *)malloc(sizeof(roaring_bitmap_t)); + if (ans == NULL) { + return NULL; + } + size_t bytesread; + bool is_ok = ra_portable_deserialize(&ans->high_low_container, buf, maxbytes, &bytesread); + if(is_ok) assert(bytesread <= maxbytes); + roaring_bitmap_set_copy_on_write(ans, false); + if (!is_ok) { + free(ans); + return NULL; + } + return ans; +} + +roaring_bitmap_t *roaring_bitmap_portable_deserialize(const char *buf) { + return roaring_bitmap_portable_deserialize_safe(buf, SIZE_MAX); +} + + +size_t roaring_bitmap_portable_deserialize_size(const char *buf, size_t maxbytes) { + return ra_portable_deserialize_size(buf, maxbytes); +} + + +size_t roaring_bitmap_portable_serialize(const roaring_bitmap_t *ra, + char *buf) { + return ra_portable_serialize(&ra->high_low_container, buf); +} + +roaring_bitmap_t *roaring_bitmap_deserialize(const void *buf) { + const char *bufaschar = (const char *)buf; + if (*(const unsigned char *)buf == SERIALIZATION_ARRAY_UINT32) { + /* This looks like a compressed set of uint32_t elements */ + uint32_t card; + memcpy(&card, bufaschar + 1, sizeof(uint32_t)); + const uint32_t *elems = + (const uint32_t *)(bufaschar + 1 + sizeof(uint32_t)); + + return roaring_bitmap_of_ptr(card, elems); + } else if (bufaschar[0] == SERIALIZATION_CONTAINER) { + return roaring_bitmap_portable_deserialize(bufaschar + 1); + } else + return (NULL); +} + +bool roaring_iterate(const roaring_bitmap_t *ra, roaring_iterator iterator, + void *ptr) { + for (int i = 0; i < ra->high_low_container.size; ++i) + if (!container_iterate(ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i], + ((uint32_t)ra->high_low_container.keys[i]) << 16, + iterator, ptr)) { + return false; + } + return true; +} + +bool roaring_iterate64(const roaring_bitmap_t *ra, roaring_iterator64 iterator, + uint64_t high_bits, void *ptr) { + for (int i = 0; i < ra->high_low_container.size; ++i) + if (!container_iterate64( + ra->high_low_container.containers[i], + ra->high_low_container.typecodes[i], + ((uint32_t)ra->high_low_container.keys[i]) << 16, iterator, + high_bits, ptr)) { + return false; + } + return true; +} + +/**** +* begin roaring_uint32_iterator_t +*****/ + +// Partially initializes the roaring iterator when it begins looking at +// a new container. +static bool iter_new_container_partial_init(roaring_uint32_iterator_t *newit) { + newit->in_container_index = 0; + newit->run_index = 0; + newit->current_value = 0; + if (newit->container_index >= newit->parent->high_low_container.size || + newit->container_index < 0) { + newit->current_value = UINT32_MAX; + return (newit->has_value = false); + } + // assume not empty + newit->has_value = true; + // we precompute container, typecode and highbits so that successive + // iterators do not have to grab them from odd memory locations + // and have to worry about the (easily predicted) container_unwrap_shared + // call. + newit->container = + newit->parent->high_low_container.containers[newit->container_index]; + newit->typecode = + newit->parent->high_low_container.typecodes[newit->container_index]; + newit->highbits = + ((uint32_t) + newit->parent->high_low_container.keys[newit->container_index]) + << 16; + newit->container = + container_unwrap_shared(newit->container, &(newit->typecode)); + return newit->has_value; +} + +static bool loadfirstvalue(roaring_uint32_iterator_t *newit) { + if (!iter_new_container_partial_init(newit)) + return newit->has_value; + + uint32_t wordindex; + uint64_t word; // used for bitsets + switch (newit->typecode) { + case BITSET_CONTAINER_TYPE_CODE: + wordindex = 0; + while ((word = ((const bitset_container_t *)(newit->container)) + ->array[wordindex]) == 0) + wordindex++; // advance + // here "word" is non-zero + newit->in_container_index = wordindex * 64 + __builtin_ctzll(word); + newit->current_value = newit->highbits | newit->in_container_index; + break; + case ARRAY_CONTAINER_TYPE_CODE: + newit->current_value = + newit->highbits | + ((const array_container_t *)(newit->container))->array[0]; + break; + case RUN_CONTAINER_TYPE_CODE: + newit->current_value = + newit->highbits | + (((const run_container_t *)(newit->container))->runs[0].value); + break; + default: + // if this ever happens, bug! + assert(false); + } // switch (typecode) + return true; +} + +static bool loadlastvalue(roaring_uint32_iterator_t* newit) { + if (!iter_new_container_partial_init(newit)) + return newit->has_value; + + switch(newit->typecode) { + case BITSET_CONTAINER_TYPE_CODE: { + uint32_t wordindex = BITSET_CONTAINER_SIZE_IN_WORDS - 1; + uint64_t word; + const bitset_container_t* bitset_container = (const bitset_container_t*)newit->container; + while ((word = bitset_container->array[wordindex]) == 0) + --wordindex; + + int num_leading_zeros = __builtin_clzll(word); + newit->in_container_index = (wordindex * 64) + (63 - num_leading_zeros); + newit->current_value = newit->highbits | newit->in_container_index; + break; + } + case ARRAY_CONTAINER_TYPE_CODE: { + const array_container_t* array_container = (const array_container_t*)newit->container; + newit->in_container_index = array_container->cardinality - 1; + newit->current_value = newit->highbits | array_container->array[newit->in_container_index]; + break; + } + case RUN_CONTAINER_TYPE_CODE: { + const run_container_t* run_container = (const run_container_t*)newit->container; + newit->run_index = run_container->n_runs - 1; + const rle16_t* last_run = &run_container->runs[newit->run_index]; + newit->current_value = newit->highbits | (last_run->value + last_run->length); + break; + } + default: + // if this ever happens, bug! + assert(false); + } + return true; +} + +// prerequesite: the value should be in range of the container +static bool loadfirstvalue_largeorequal(roaring_uint32_iterator_t *newit, uint32_t val) { + // Don't have to check return value because of prerequisite + iter_new_container_partial_init(newit); + uint16_t lb = val & 0xFFFF; + + switch (newit->typecode) { + case BITSET_CONTAINER_TYPE_CODE: + newit->in_container_index = bitset_container_index_equalorlarger((const bitset_container_t *)(newit->container), lb); + newit->current_value = newit->highbits | newit->in_container_index; + break; + case ARRAY_CONTAINER_TYPE_CODE: + newit->in_container_index = array_container_index_equalorlarger((const array_container_t *)(newit->container), lb); + newit->current_value = + newit->highbits | + ((const array_container_t *)(newit->container))->array[newit->in_container_index]; + break; + case RUN_CONTAINER_TYPE_CODE: + newit->run_index = run_container_index_equalorlarger((const run_container_t *)(newit->container), lb); + if(((const run_container_t *)(newit->container))->runs[newit->run_index].value <= lb) { + newit->current_value = val; + } else { + newit->current_value = + newit->highbits | + (((const run_container_t *)(newit->container))->runs[newit->run_index].value); + } + break; + default: + // if this ever happens, bug! + assert(false); + } // switch (typecode) + return true; +} + +void roaring_init_iterator(const roaring_bitmap_t *ra, + roaring_uint32_iterator_t *newit) { + newit->parent = ra; + newit->container_index = 0; + newit->has_value = loadfirstvalue(newit); +} + +void roaring_init_iterator_last(const roaring_bitmap_t *ra, + roaring_uint32_iterator_t *newit) { + newit->parent = ra; + newit->container_index = newit->parent->high_low_container.size - 1; + newit->has_value = loadlastvalue(newit); +} + +roaring_uint32_iterator_t *roaring_create_iterator(const roaring_bitmap_t *ra) { + roaring_uint32_iterator_t *newit = + (roaring_uint32_iterator_t *)malloc(sizeof(roaring_uint32_iterator_t)); + if (newit == NULL) return NULL; + roaring_init_iterator(ra, newit); + return newit; +} + +roaring_uint32_iterator_t *roaring_copy_uint32_iterator( + const roaring_uint32_iterator_t *it) { + roaring_uint32_iterator_t *newit = + (roaring_uint32_iterator_t *)malloc(sizeof(roaring_uint32_iterator_t)); + memcpy(newit, it, sizeof(roaring_uint32_iterator_t)); + return newit; +} + +bool roaring_move_uint32_iterator_equalorlarger(roaring_uint32_iterator_t *it, uint32_t val) { + uint16_t hb = val >> 16; + const int i = ra_get_index(& it->parent->high_low_container, hb); + if (i >= 0) { + uint32_t lowvalue = container_maximum(it->parent->high_low_container.containers[i], it->parent->high_low_container.typecodes[i]); + uint16_t lb = val & 0xFFFF; + if(lowvalue < lb ) { + it->container_index = i+1; // will have to load first value of next container + } else {// the value is necessarily within the range of the container + it->container_index = i; + it->has_value = loadfirstvalue_largeorequal(it, val); + return it->has_value; + } + } else { + // there is no matching, so we are going for the next container + it->container_index = -i-1; + } + it->has_value = loadfirstvalue(it); + return it->has_value; +} + + +bool roaring_advance_uint32_iterator(roaring_uint32_iterator_t *it) { + if (it->container_index >= it->parent->high_low_container.size) { + return (it->has_value = false); + } + if (it->container_index < 0) { + it->container_index = 0; + return (it->has_value = loadfirstvalue(it)); + } + + uint32_t wordindex; // used for bitsets + uint64_t word; // used for bitsets + switch (it->typecode) { + case BITSET_CONTAINER_TYPE_CODE: + it->in_container_index++; + wordindex = it->in_container_index / 64; + if (wordindex >= BITSET_CONTAINER_SIZE_IN_WORDS) break; + word = ((const bitset_container_t *)(it->container)) + ->array[wordindex] & + (UINT64_MAX << (it->in_container_index % 64)); + // next part could be optimized/simplified + while ((word == 0) && + (wordindex + 1 < BITSET_CONTAINER_SIZE_IN_WORDS)) { + wordindex++; + word = ((const bitset_container_t *)(it->container)) + ->array[wordindex]; + } + if (word != 0) { + it->in_container_index = wordindex * 64 + __builtin_ctzll(word); + it->current_value = it->highbits | it->in_container_index; + return (it->has_value = true); + } + break; + case ARRAY_CONTAINER_TYPE_CODE: + it->in_container_index++; + if (it->in_container_index < + ((const array_container_t *)(it->container))->cardinality) { + it->current_value = it->highbits | + ((const array_container_t *)(it->container)) + ->array[it->in_container_index]; + return (it->has_value = true); + } + break; + case RUN_CONTAINER_TYPE_CODE: { + if(it->current_value == UINT32_MAX) { + return (it->has_value = false); // without this, we risk an overflow to zero + } + + const run_container_t* run_container = (const run_container_t*)it->container; + if (++it->current_value <= (it->highbits | (run_container->runs[it->run_index].value + + run_container->runs[it->run_index].length))) { + return (it->has_value = true); + } + + if (++it->run_index < run_container->n_runs) { + // Assume the run has a value + it->current_value = it->highbits | run_container->runs[it->run_index].value; + return (it->has_value = true); + } + break; + } + default: + // if this ever happens, bug! + assert(false); + } // switch (typecode) + // moving to next container + it->container_index++; + return (it->has_value = loadfirstvalue(it)); +} + +bool roaring_previous_uint32_iterator(roaring_uint32_iterator_t *it) { + if (it->container_index < 0) { + return (it->has_value = false); + } + if (it->container_index >= it->parent->high_low_container.size) { + it->container_index = it->parent->high_low_container.size - 1; + return (it->has_value = loadlastvalue(it)); + } + + switch (it->typecode) { + case BITSET_CONTAINER_TYPE_CODE: { + if (--it->in_container_index < 0) + break; + + const bitset_container_t* bitset_container = (const bitset_container_t*)it->container; + int32_t wordindex = it->in_container_index / 64; + uint64_t word = bitset_container->array[wordindex] & (UINT64_MAX >> (63 - (it->in_container_index % 64))); + + while (word == 0 && --wordindex >= 0) { + word = bitset_container->array[wordindex]; + } + if (word == 0) + break; + + int num_leading_zeros = __builtin_clzll(word); + it->in_container_index = (wordindex * 64) + (63 - num_leading_zeros); + it->current_value = it->highbits | it->in_container_index; + return (it->has_value = true); + } + case ARRAY_CONTAINER_TYPE_CODE: { + if (--it->in_container_index < 0) + break; + + const array_container_t* array_container = (const array_container_t*)it->container; + it->current_value = it->highbits | array_container->array[it->in_container_index]; + return (it->has_value = true); + } + case RUN_CONTAINER_TYPE_CODE: { + if(it->current_value == 0) + return (it->has_value = false); + + const run_container_t* run_container = (const run_container_t*)it->container; + if (--it->current_value >= (it->highbits | run_container->runs[it->run_index].value)) { + return (it->has_value = true); + } + + if (--it->run_index < 0) + break; + + it->current_value = it->highbits | (run_container->runs[it->run_index].value + + run_container->runs[it->run_index].length); + return (it->has_value = true); + } + default: + // if this ever happens, bug! + assert(false); + } // switch (typecode) + + // moving to previous container + it->container_index--; + return (it->has_value = loadlastvalue(it)); +} + +uint32_t roaring_read_uint32_iterator(roaring_uint32_iterator_t *it, uint32_t* buf, uint32_t count) { + uint32_t ret = 0; + uint32_t num_values; + uint32_t wordindex; // used for bitsets + uint64_t word; // used for bitsets + const array_container_t* acont; //TODO remove + const run_container_t* rcont; //TODO remove + const bitset_container_t* bcont; //TODO remove + + while (it->has_value && ret < count) { + switch (it->typecode) { + case BITSET_CONTAINER_TYPE_CODE: + bcont = (const bitset_container_t*)(it->container); + wordindex = it->in_container_index / 64; + word = bcont->array[wordindex] & (UINT64_MAX << (it->in_container_index % 64)); + do { + while (word != 0 && ret < count) { + buf[0] = it->highbits | (wordindex * 64 + __builtin_ctzll(word)); + word = word & (word - 1); + buf++; + ret++; + } + while (word == 0 && wordindex+1 < BITSET_CONTAINER_SIZE_IN_WORDS) { + wordindex++; + word = bcont->array[wordindex]; + } + } while (word != 0 && ret < count); + it->has_value = (word != 0); + if (it->has_value) { + it->in_container_index = wordindex * 64 + __builtin_ctzll(word); + it->current_value = it->highbits | it->in_container_index; + } + break; + case ARRAY_CONTAINER_TYPE_CODE: + acont = (const array_container_t *)(it->container); + num_values = minimum_uint32(acont->cardinality - it->in_container_index, count - ret); + for (uint32_t i = 0; i < num_values; i++) { + buf[i] = it->highbits | acont->array[it->in_container_index + i]; + } + buf += num_values; + ret += num_values; + it->in_container_index += num_values; + it->has_value = (it->in_container_index < acont->cardinality); + if (it->has_value) { + it->current_value = it->highbits | acont->array[it->in_container_index]; + } + break; + case RUN_CONTAINER_TYPE_CODE: + rcont = (const run_container_t*)(it->container); + //"in_run_index" name is misleading, read it as "max_value_in_current_run" + do { + uint32_t largest_run_value = it->highbits | (rcont->runs[it->run_index].value + rcont->runs[it->run_index].length); + num_values = minimum_uint32(largest_run_value - it->current_value + 1, count - ret); + for (uint32_t i = 0; i < num_values; i++) { + buf[i] = it->current_value + i; + } + it->current_value += num_values; // this can overflow to zero: UINT32_MAX+1=0 + buf += num_values; + ret += num_values; + + if (it->current_value > largest_run_value || it->current_value == 0) { + it->run_index++; + if (it->run_index < rcont->n_runs) { + it->current_value = it->highbits | rcont->runs[it->run_index].value; + } else { + it->has_value = false; + } + } + } while ((ret < count) && it->has_value); + break; + default: + assert(false); + } + if (it->has_value) { + assert(ret == count); + return ret; + } + it->container_index++; + it->has_value = loadfirstvalue(it); + } + return ret; +} + + + +void roaring_free_uint32_iterator(roaring_uint32_iterator_t *it) { free(it); } + +/**** +* end of roaring_uint32_iterator_t +*****/ + +bool roaring_bitmap_equals(const roaring_bitmap_t *ra1, + const roaring_bitmap_t *ra2) { + if (ra1->high_low_container.size != ra2->high_low_container.size) { + return false; + } + for (int i = 0; i < ra1->high_low_container.size; ++i) { + if (ra1->high_low_container.keys[i] != + ra2->high_low_container.keys[i]) { + return false; + } + } + for (int i = 0; i < ra1->high_low_container.size; ++i) { + bool areequal = container_equals(ra1->high_low_container.containers[i], + ra1->high_low_container.typecodes[i], + ra2->high_low_container.containers[i], + ra2->high_low_container.typecodes[i]); + if (!areequal) { + return false; + } + } + return true; +} + +bool roaring_bitmap_is_subset(const roaring_bitmap_t *ra1, + const roaring_bitmap_t *ra2) { + const int length1 = ra1->high_low_container.size, + length2 = ra2->high_low_container.size; + + int pos1 = 0, pos2 = 0; + + while (pos1 < length1 && pos2 < length2) { + const uint16_t s1 = ra_get_key_at_index(&ra1->high_low_container, pos1); + const uint16_t s2 = ra_get_key_at_index(&ra2->high_low_container, pos2); + + if (s1 == s2) { + uint8_t container_type_1, container_type_2; + void *c1 = ra_get_container_at_index(&ra1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&ra2->high_low_container, pos2, + &container_type_2); + bool subset = + container_is_subset(c1, container_type_1, c2, container_type_2); + if (!subset) return false; + ++pos1; + ++pos2; + } else if (s1 < s2) { // s1 < s2 + return false; + } else { // s1 > s2 + pos2 = ra_advance_until(&ra2->high_low_container, s1, pos2); + } + } + if (pos1 == length1) + return true; + else + return false; +} + +static void insert_flipped_container(roaring_array_t *ans_arr, + const roaring_array_t *x1_arr, uint16_t hb, + uint16_t lb_start, uint16_t lb_end) { + const int i = ra_get_index(x1_arr, hb); + const int j = ra_get_index(ans_arr, hb); + uint8_t ctype_in, ctype_out; + void *flipped_container = NULL; + if (i >= 0) { + void *container_to_flip = + ra_get_container_at_index(x1_arr, i, &ctype_in); + flipped_container = + container_not_range(container_to_flip, ctype_in, (uint32_t)lb_start, + (uint32_t)(lb_end + 1), &ctype_out); + + if (container_get_cardinality(flipped_container, ctype_out)) + ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container, + ctype_out); + else { + container_free(flipped_container, ctype_out); + } + } else { + flipped_container = container_range_of_ones( + (uint32_t)lb_start, (uint32_t)(lb_end + 1), &ctype_out); + ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container, + ctype_out); + } +} + +static void inplace_flip_container(roaring_array_t *x1_arr, uint16_t hb, + uint16_t lb_start, uint16_t lb_end) { + const int i = ra_get_index(x1_arr, hb); + uint8_t ctype_in, ctype_out; + void *flipped_container = NULL; + if (i >= 0) { + void *container_to_flip = + ra_get_container_at_index(x1_arr, i, &ctype_in); + flipped_container = container_inot_range( + container_to_flip, ctype_in, (uint32_t)lb_start, + (uint32_t)(lb_end + 1), &ctype_out); + // if a new container was created, the old one was already freed + if (container_get_cardinality(flipped_container, ctype_out)) { + ra_set_container_at_index(x1_arr, i, flipped_container, ctype_out); + } else { + container_free(flipped_container, ctype_out); + ra_remove_at_index(x1_arr, i); + } + + } else { + flipped_container = container_range_of_ones( + (uint32_t)lb_start, (uint32_t)(lb_end + 1), &ctype_out); + ra_insert_new_key_value_at(x1_arr, -i - 1, hb, flipped_container, + ctype_out); + } +} + +static void insert_fully_flipped_container(roaring_array_t *ans_arr, + const roaring_array_t *x1_arr, + uint16_t hb) { + const int i = ra_get_index(x1_arr, hb); + const int j = ra_get_index(ans_arr, hb); + uint8_t ctype_in, ctype_out; + void *flipped_container = NULL; + if (i >= 0) { + void *container_to_flip = + ra_get_container_at_index(x1_arr, i, &ctype_in); + flipped_container = + container_not(container_to_flip, ctype_in, &ctype_out); + if (container_get_cardinality(flipped_container, ctype_out)) + ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container, + ctype_out); + else { + container_free(flipped_container, ctype_out); + } + } else { + flipped_container = container_range_of_ones(0U, 0x10000U, &ctype_out); + ra_insert_new_key_value_at(ans_arr, -j - 1, hb, flipped_container, + ctype_out); + } +} + +static void inplace_fully_flip_container(roaring_array_t *x1_arr, uint16_t hb) { + const int i = ra_get_index(x1_arr, hb); + uint8_t ctype_in, ctype_out; + void *flipped_container = NULL; + if (i >= 0) { + void *container_to_flip = + ra_get_container_at_index(x1_arr, i, &ctype_in); + flipped_container = + container_inot(container_to_flip, ctype_in, &ctype_out); + + if (container_get_cardinality(flipped_container, ctype_out)) { + ra_set_container_at_index(x1_arr, i, flipped_container, ctype_out); + } else { + container_free(flipped_container, ctype_out); + ra_remove_at_index(x1_arr, i); + } + + } else { + flipped_container = container_range_of_ones(0U, 0x10000U, &ctype_out); + ra_insert_new_key_value_at(x1_arr, -i - 1, hb, flipped_container, + ctype_out); + } +} + +roaring_bitmap_t *roaring_bitmap_flip(const roaring_bitmap_t *x1, + uint64_t range_start, + uint64_t range_end) { + if (range_start >= range_end) { + return roaring_bitmap_copy(x1); + } + if(range_end >= UINT64_C(0x100000000)) { + range_end = UINT64_C(0x100000000); + } + + roaring_bitmap_t *ans = roaring_bitmap_create(); + roaring_bitmap_set_copy_on_write(ans, is_cow(x1)); + + uint16_t hb_start = (uint16_t)(range_start >> 16); + const uint16_t lb_start = (uint16_t)range_start; // & 0xFFFF; + uint16_t hb_end = (uint16_t)((range_end - 1) >> 16); + const uint16_t lb_end = (uint16_t)(range_end - 1); // & 0xFFFF; + + ra_append_copies_until(&ans->high_low_container, &x1->high_low_container, + hb_start, is_cow(x1)); + if (hb_start == hb_end) { + insert_flipped_container(&ans->high_low_container, + &x1->high_low_container, hb_start, lb_start, + lb_end); + } else { + // start and end containers are distinct + if (lb_start > 0) { + // handle first (partial) container + insert_flipped_container(&ans->high_low_container, + &x1->high_low_container, hb_start, + lb_start, 0xFFFF); + ++hb_start; // for the full containers. Can't wrap. + } + + if (lb_end != 0xFFFF) --hb_end; // later we'll handle the partial block + + for (uint32_t hb = hb_start; hb <= hb_end; ++hb) { + insert_fully_flipped_container(&ans->high_low_container, + &x1->high_low_container, hb); + } + + // handle a partial final container + if (lb_end != 0xFFFF) { + insert_flipped_container(&ans->high_low_container, + &x1->high_low_container, hb_end + 1, 0, + lb_end); + ++hb_end; + } + } + ra_append_copies_after(&ans->high_low_container, &x1->high_low_container, + hb_end, is_cow(x1)); + return ans; +} + +void roaring_bitmap_flip_inplace(roaring_bitmap_t *x1, uint64_t range_start, + uint64_t range_end) { + if (range_start >= range_end) { + return; // empty range + } + if(range_end >= UINT64_C(0x100000000)) { + range_end = UINT64_C(0x100000000); + } + + uint16_t hb_start = (uint16_t)(range_start >> 16); + const uint16_t lb_start = (uint16_t)range_start; + uint16_t hb_end = (uint16_t)((range_end - 1) >> 16); + const uint16_t lb_end = (uint16_t)(range_end - 1); + + if (hb_start == hb_end) { + inplace_flip_container(&x1->high_low_container, hb_start, lb_start, + lb_end); + } else { + // start and end containers are distinct + if (lb_start > 0) { + // handle first (partial) container + inplace_flip_container(&x1->high_low_container, hb_start, lb_start, + 0xFFFF); + ++hb_start; // for the full containers. Can't wrap. + } + + if (lb_end != 0xFFFF) --hb_end; + + for (uint32_t hb = hb_start; hb <= hb_end; ++hb) { + inplace_fully_flip_container(&x1->high_low_container, hb); + } + // handle a partial final container + if (lb_end != 0xFFFF) { + inplace_flip_container(&x1->high_low_container, hb_end + 1, 0, + lb_end); + ++hb_end; + } + } +} + +roaring_bitmap_t *roaring_bitmap_lazy_or(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2, + const bool bitsetconversion) { + uint8_t container_result_type = 0; + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + if (0 == length1) { + return roaring_bitmap_copy(x2); + } + if (0 == length2) { + return roaring_bitmap_copy(x1); + } + roaring_bitmap_t *answer = + roaring_bitmap_create_with_capacity(length1 + length2); + roaring_bitmap_set_copy_on_write(answer, is_cow(x1) && is_cow(x2)); + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c; + if (bitsetconversion && (get_container_type(c1, container_type_1) != + BITSET_CONTAINER_TYPE_CODE) && + (get_container_type(c2, container_type_2) != + BITSET_CONTAINER_TYPE_CODE)) { + void *newc1 = + container_mutable_unwrap_shared(c1, &container_type_1); + newc1 = container_to_bitset(newc1, container_type_1); + container_type_1 = BITSET_CONTAINER_TYPE_CODE; + c = container_lazy_ior(newc1, container_type_1, c2, + container_type_2, + &container_result_type); + if (c != newc1) { // should not happen + container_free(newc1, container_type_1); + } + } else { + c = container_lazy_or(c1, container_type_1, c2, + container_type_2, &container_result_type); + } + // since we assume that the initial containers are non-empty, + // the + // result here + // can only be non-empty + ra_append(&answer->high_low_container, s1, c, + container_result_type); + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + c1 = + get_copy_of_container(c1, &container_type_1, is_cow(x1)); + if (is_cow(x1)) { + ra_set_container_at_index(&x1->high_low_container, pos1, c1, + container_type_1); + } + ra_append(&answer->high_low_container, s1, c1, container_type_1); + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + ra_append(&answer->high_low_container, s2, c2, container_type_2); + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&answer->high_low_container, + &x2->high_low_container, pos2, length2, + is_cow(x2)); + } else if (pos2 == length2) { + ra_append_copy_range(&answer->high_low_container, + &x1->high_low_container, pos1, length1, + is_cow(x1)); + } + return answer; +} + +void roaring_bitmap_lazy_or_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2, + const bool bitsetconversion) { + uint8_t container_result_type = 0; + int length1 = x1->high_low_container.size; + const int length2 = x2->high_low_container.size; + + if (0 == length2) return; + + if (0 == length1) { + roaring_bitmap_overwrite(x1, x2); + return; + } + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + if (!container_is_full(c1, container_type_1)) { + if ((bitsetconversion == false) || + (get_container_type(c1, container_type_1) == + BITSET_CONTAINER_TYPE_CODE)) { + c1 = get_writable_copy_if_shared(c1, &container_type_1); + } else { + // convert to bitset + void *oldc1 = c1; + uint8_t oldt1 = container_type_1; + c1 = container_mutable_unwrap_shared(c1, &container_type_1); + c1 = container_to_bitset(c1, container_type_1); + container_free(oldc1, oldt1); + container_type_1 = BITSET_CONTAINER_TYPE_CODE; + } + + void *c2 = ra_get_container_at_index(&x2->high_low_container, + pos2, &container_type_2); + void *c = container_lazy_ior(c1, container_type_1, c2, + container_type_2, + &container_result_type); + if (c != + c1) { // in this instance a new container was created, and + // we need to free the old one + container_free(c1, container_type_1); + } + + ra_set_container_at_index(&x1->high_low_container, pos1, c, + container_result_type); + } + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + // void *c2_clone = container_clone(c2, container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + ra_insert_new_key_value_at(&x1->high_low_container, pos1, s2, c2, + container_type_2); + pos1++; + length1++; + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&x1->high_low_container, &x2->high_low_container, + pos2, length2, is_cow(x2)); + } +} + +roaring_bitmap_t *roaring_bitmap_lazy_xor(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + if (0 == length1) { + return roaring_bitmap_copy(x2); + } + if (0 == length2) { + return roaring_bitmap_copy(x1); + } + roaring_bitmap_t *answer = + roaring_bitmap_create_with_capacity(length1 + length2); + roaring_bitmap_set_copy_on_write(answer, is_cow(x1) && is_cow(x2)); + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = + container_lazy_xor(c1, container_type_1, c2, container_type_2, + &container_result_type); + + if (container_nonzero_cardinality(c, container_result_type)) { + ra_append(&answer->high_low_container, s1, c, + container_result_type); + } else { + container_free(c, container_result_type); + } + + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + c1 = + get_copy_of_container(c1, &container_type_1, is_cow(x1)); + if (is_cow(x1)) { + ra_set_container_at_index(&x1->high_low_container, pos1, c1, + container_type_1); + } + ra_append(&answer->high_low_container, s1, c1, container_type_1); + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + ra_append(&answer->high_low_container, s2, c2, container_type_2); + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&answer->high_low_container, + &x2->high_low_container, pos2, length2, + is_cow(x2)); + } else if (pos2 == length2) { + ra_append_copy_range(&answer->high_low_container, + &x1->high_low_container, pos1, length1, + is_cow(x1)); + } + return answer; +} + +void roaring_bitmap_lazy_xor_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + assert(x1 != x2); + uint8_t container_result_type = 0; + int length1 = x1->high_low_container.size; + const int length2 = x2->high_low_container.size; + + if (0 == length2) return; + + if (0 == length1) { + roaring_bitmap_overwrite(x1, x2); + return; + } + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + c1 = get_writable_copy_if_shared(c1, &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + void *c = + container_lazy_ixor(c1, container_type_1, c2, container_type_2, + &container_result_type); + if (container_nonzero_cardinality(c, container_result_type)) { + ra_set_container_at_index(&x1->high_low_container, pos1, c, + container_result_type); + ++pos1; + } else { + container_free(c, container_result_type); + ra_remove_at_index(&x1->high_low_container, pos1); + --length1; + } + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + // void *c2_clone = container_clone(c2, container_type_2); + c2 = + get_copy_of_container(c2, &container_type_2, is_cow(x2)); + if (is_cow(x2)) { + ra_set_container_at_index(&x2->high_low_container, pos2, c2, + container_type_2); + } + ra_insert_new_key_value_at(&x1->high_low_container, pos1, s2, c2, + container_type_2); + pos1++; + length1++; + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_copy_range(&x1->high_low_container, &x2->high_low_container, + pos2, length2, is_cow(x2)); + } +} + +void roaring_bitmap_repair_after_lazy(roaring_bitmap_t *ra) { + for (int i = 0; i < ra->high_low_container.size; ++i) { + const uint8_t original_typecode = ra->high_low_container.typecodes[i]; + void *container = ra->high_low_container.containers[i]; + uint8_t new_typecode = original_typecode; + void *newcontainer = + container_repair_after_lazy(container, &new_typecode); + ra->high_low_container.containers[i] = newcontainer; + ra->high_low_container.typecodes[i] = new_typecode; + } +} + + + +/** +* roaring_bitmap_rank returns the number of integers that are smaller or equal +* to x. +*/ +uint64_t roaring_bitmap_rank(const roaring_bitmap_t *bm, uint32_t x) { + uint64_t size = 0; + uint32_t xhigh = x >> 16; + for (int i = 0; i < bm->high_low_container.size; i++) { + uint32_t key = bm->high_low_container.keys[i]; + if (xhigh > key) { + size += + container_get_cardinality(bm->high_low_container.containers[i], + bm->high_low_container.typecodes[i]); + } else if (xhigh == key) { + return size + container_rank(bm->high_low_container.containers[i], + bm->high_low_container.typecodes[i], + x & 0xFFFF); + } else { + return size; + } + } + return size; +} + +/** +* roaring_bitmap_smallest returns the smallest value in the set. +* Returns UINT32_MAX if the set is empty. +*/ +uint32_t roaring_bitmap_minimum(const roaring_bitmap_t *bm) { + if (bm->high_low_container.size > 0) { + void *container = bm->high_low_container.containers[0]; + uint8_t typecode = bm->high_low_container.typecodes[0]; + uint32_t key = bm->high_low_container.keys[0]; + uint32_t lowvalue = container_minimum(container, typecode); + return lowvalue | (key << 16); + } + return UINT32_MAX; +} + +/** +* roaring_bitmap_smallest returns the greatest value in the set. +* Returns 0 if the set is empty. +*/ +uint32_t roaring_bitmap_maximum(const roaring_bitmap_t *bm) { + if (bm->high_low_container.size > 0) { + void *container = + bm->high_low_container.containers[bm->high_low_container.size - 1]; + uint8_t typecode = + bm->high_low_container.typecodes[bm->high_low_container.size - 1]; + uint32_t key = + bm->high_low_container.keys[bm->high_low_container.size - 1]; + uint32_t lowvalue = container_maximum(container, typecode); + return lowvalue | (key << 16); + } + return 0; +} + +bool roaring_bitmap_select(const roaring_bitmap_t *bm, uint32_t rank, + uint32_t *element) { + void *container; + uint8_t typecode; + uint16_t key; + uint32_t start_rank = 0; + int i = 0; + bool valid = false; + while (!valid && i < bm->high_low_container.size) { + container = bm->high_low_container.containers[i]; + typecode = bm->high_low_container.typecodes[i]; + valid = + container_select(container, typecode, &start_rank, rank, element); + i++; + } + + if (valid) { + key = bm->high_low_container.keys[i - 1]; + *element |= (key << 16); + return true; + } else + return false; +} + +bool roaring_bitmap_intersect(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + uint64_t answer = 0; + int pos1 = 0, pos2 = 0; + + while (pos1 < length1 && pos2 < length2) { + const uint16_t s1 = ra_get_key_at_index(& x1->high_low_container, pos1); + const uint16_t s2 = ra_get_key_at_index(& x2->high_low_container, pos2); + + if (s1 == s2) { + uint8_t container_type_1, container_type_2; + void *c1 = ra_get_container_at_index(& x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(& x2->high_low_container, pos2, + &container_type_2); + if( container_intersect(c1, container_type_1, c2, container_type_2) ) return true; + ++pos1; + ++pos2; + } else if (s1 < s2) { // s1 < s2 + pos1 = ra_advance_until(& x1->high_low_container, s2, pos1); + } else { // s1 > s2 + pos2 = ra_advance_until(& x2->high_low_container, s1, pos2); + } + } + return answer; +} + + +uint64_t roaring_bitmap_and_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + const int length1 = x1->high_low_container.size, + length2 = x2->high_low_container.size; + uint64_t answer = 0; + int pos1 = 0, pos2 = 0; + + while (pos1 < length1 && pos2 < length2) { + const uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + const uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + if (s1 == s2) { + uint8_t container_type_1, container_type_2; + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + answer += container_and_cardinality(c1, container_type_1, c2, + container_type_2); + ++pos1; + ++pos2; + } else if (s1 < s2) { // s1 < s2 + pos1 = ra_advance_until(&x1->high_low_container, s2, pos1); + } else { // s1 > s2 + pos2 = ra_advance_until(&x2->high_low_container, s1, pos2); + } + } + return answer; +} + +double roaring_bitmap_jaccard_index(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + const uint64_t c1 = roaring_bitmap_get_cardinality(x1); + const uint64_t c2 = roaring_bitmap_get_cardinality(x2); + const uint64_t inter = roaring_bitmap_and_cardinality(x1, x2); + return (double)inter / (double)(c1 + c2 - inter); +} + +uint64_t roaring_bitmap_or_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + const uint64_t c1 = roaring_bitmap_get_cardinality(x1); + const uint64_t c2 = roaring_bitmap_get_cardinality(x2); + const uint64_t inter = roaring_bitmap_and_cardinality(x1, x2); + return c1 + c2 - inter; +} + +uint64_t roaring_bitmap_andnot_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + const uint64_t c1 = roaring_bitmap_get_cardinality(x1); + const uint64_t inter = roaring_bitmap_and_cardinality(x1, x2); + return c1 - inter; +} + +uint64_t roaring_bitmap_xor_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2) { + const uint64_t c1 = roaring_bitmap_get_cardinality(x1); + const uint64_t c2 = roaring_bitmap_get_cardinality(x2); + const uint64_t inter = roaring_bitmap_and_cardinality(x1, x2); + return c1 + c2 - 2 * inter; +} + + +/** + * Check whether a range of values from range_start (included) to range_end (excluded) is present + */ +bool roaring_bitmap_contains_range(const roaring_bitmap_t *r, uint64_t range_start, uint64_t range_end) { + if(range_end >= UINT64_C(0x100000000)) { + range_end = UINT64_C(0x100000000); + } + if (range_start >= range_end) return true; // empty range are always contained! + if (range_end - range_start == 1) return roaring_bitmap_contains(r, (uint32_t)range_start); + uint16_t hb_rs = (uint16_t)(range_start >> 16); + uint16_t hb_re = (uint16_t)((range_end - 1) >> 16); + const int32_t span = hb_re - hb_rs; + const int32_t hlc_sz = ra_get_size(&r->high_low_container); + if (hlc_sz < span + 1) { + return false; + } + int32_t is = ra_get_index(&r->high_low_container, hb_rs); + int32_t ie = ra_get_index(&r->high_low_container, hb_re); + ie = (ie < 0 ? -ie - 1 : ie); + if ((is < 0) || ((ie - is) != span)) { + return false; + } + const uint32_t lb_rs = range_start & 0xFFFF; + const uint32_t lb_re = ((range_end - 1) & 0xFFFF) + 1; + uint8_t typecode; + void *container = ra_get_container_at_index(&r->high_low_container, is, &typecode); + if (hb_rs == hb_re) { + return container_contains_range(container, lb_rs, lb_re, typecode); + } + if (!container_contains_range(container, lb_rs, 1 << 16, typecode)) { + return false; + } + assert(ie < hlc_sz); // would indicate an algorithmic bug + container = ra_get_container_at_index(&r->high_low_container, ie, &typecode); + if (!container_contains_range(container, 0, lb_re, typecode)) { + return false; + } + for (int32_t i = is + 1; i < ie; ++i) { + container = ra_get_container_at_index(&r->high_low_container, i, &typecode); + if (!container_is_full(container, typecode) ) { + return false; + } + } + return true; +} + + +bool roaring_bitmap_is_strict_subset(const roaring_bitmap_t *ra1, + const roaring_bitmap_t *ra2) { + return (roaring_bitmap_get_cardinality(ra2) > + roaring_bitmap_get_cardinality(ra1) && + roaring_bitmap_is_subset(ra1, ra2)); +} + + +/* + * FROZEN SERIALIZATION FORMAT DESCRIPTION + * + * -- (beginning must be aligned by 32 bytes) -- + * uint64_t[BITSET_CONTAINER_SIZE_IN_WORDS * num_bitset_containers] + * rle16_t[total number of rle elements in all run containers] + * uint16_t[total number of array elements in all array containers] + * uint16_t[num_containers] + * uint16_t[num_containers] + * uint8_t[num_containers] + *
uint32_t + * + *
is a 4-byte value which is a bit union of FROZEN_COOKIE (15 bits) + * and the number of containers (17 bits). + * + * stores number of elements for every container. + * Its meaning depends on container type. + * For array and bitset containers, this value is the container cardinality minus one. + * For run container, it is the number of rle_t elements (n_runs). + * + * ,, are flat arrays of elements of + * all containers of respective type. + * + * <*_data> and are kept close together because they are not accessed + * during deserilization. This may reduce IO in case of large mapped bitmaps. + * All members have their native alignments during deserilization except
, + * which is not guaranteed to be aligned by 4 bytes. + */ + +size_t roaring_bitmap_frozen_size_in_bytes(const roaring_bitmap_t *rb) { + const roaring_array_t *ra = &rb->high_low_container; + size_t num_bytes = 0; + for (int32_t i = 0; i < ra->size; i++) { + switch (ra->typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: { + num_bytes += BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); + break; + } + case RUN_CONTAINER_TYPE_CODE: { + const run_container_t *run = + (const run_container_t *) ra->containers[i]; + num_bytes += run->n_runs * sizeof(rle16_t); + break; + } + case ARRAY_CONTAINER_TYPE_CODE: { + const array_container_t *array = + (const array_container_t *) ra->containers[i]; + num_bytes += array->cardinality * sizeof(uint16_t); + break; + } + default: + __builtin_unreachable(); + } + } + num_bytes += (2 + 2 + 1) * ra->size; // keys, counts, typecodes + num_bytes += 4; // header + return num_bytes; +} + +inline static void *arena_alloc(char **arena, size_t num_bytes) { + char *res = *arena; + *arena += num_bytes; + return res; +} + +void roaring_bitmap_frozen_serialize(const roaring_bitmap_t *rb, char *buf) { + /* + * Note: we do not require user to supply spicificly aligned buffer. + * Thus we have to use memcpy() everywhere. + */ + + const roaring_array_t *ra = &rb->high_low_container; + + size_t bitset_zone_size = 0; + size_t run_zone_size = 0; + size_t array_zone_size = 0; + for (int32_t i = 0; i < ra->size; i++) { + switch (ra->typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: { + bitset_zone_size += + BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); + break; + } + case RUN_CONTAINER_TYPE_CODE: { + const run_container_t *run = + (const run_container_t *) ra->containers[i]; + run_zone_size += run->n_runs * sizeof(rle16_t); + break; + } + case ARRAY_CONTAINER_TYPE_CODE: { + const array_container_t *array = + (const array_container_t *) ra->containers[i]; + array_zone_size += array->cardinality * sizeof(uint16_t); + break; + } + default: + __builtin_unreachable(); + } + } + + uint64_t *bitset_zone = (uint64_t *)arena_alloc(&buf, bitset_zone_size); + rle16_t *run_zone = (rle16_t *)arena_alloc(&buf, run_zone_size); + uint16_t *array_zone = (uint16_t *)arena_alloc(&buf, array_zone_size); + uint16_t *key_zone = (uint16_t *)arena_alloc(&buf, 2*ra->size); + uint16_t *count_zone = (uint16_t *)arena_alloc(&buf, 2*ra->size); + uint8_t *typecode_zone = (uint8_t *)arena_alloc(&buf, ra->size); + uint32_t *header_zone = (uint32_t *)arena_alloc(&buf, 4); + + for (int32_t i = 0; i < ra->size; i++) { + uint16_t count; + switch (ra->typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: { + const bitset_container_t *bitset = + (const bitset_container_t *) ra->containers[i]; + memcpy(bitset_zone, bitset->array, + BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t)); + bitset_zone += BITSET_CONTAINER_SIZE_IN_WORDS; + if (bitset->cardinality != BITSET_UNKNOWN_CARDINALITY) { + count = bitset->cardinality - 1; + } else { + count = bitset_container_compute_cardinality(bitset) - 1; + } + break; + } + case RUN_CONTAINER_TYPE_CODE: { + const run_container_t *run = + (const run_container_t *) ra->containers[i]; + size_t num_bytes = run->n_runs * sizeof(rle16_t); + memcpy(run_zone, run->runs, num_bytes); + run_zone += run->n_runs; + count = run->n_runs; + break; + } + case ARRAY_CONTAINER_TYPE_CODE: { + const array_container_t *array = + (const array_container_t *) ra->containers[i]; + size_t num_bytes = array->cardinality * sizeof(uint16_t); + memcpy(array_zone, array->array, num_bytes); + array_zone += array->cardinality; + count = array->cardinality - 1; + break; + } + default: + __builtin_unreachable(); + } + memcpy(&count_zone[i], &count, 2); + } + memcpy(key_zone, ra->keys, ra->size * sizeof(uint16_t)); + memcpy(typecode_zone, ra->typecodes, ra->size * sizeof(uint8_t)); + uint32_t header = ((uint32_t)ra->size << 15) | FROZEN_COOKIE; + memcpy(header_zone, &header, 4); +} + +const roaring_bitmap_t * +roaring_bitmap_frozen_view(const char *buf, size_t length) { + if ((uintptr_t)buf % 32 != 0) { + return NULL; + } + + // cookie and num_containers + if (length < 4) { + return NULL; + } + uint32_t header; + memcpy(&header, buf + length - 4, 4); // header may be misaligned + if ((header & 0x7FFF) != FROZEN_COOKIE) { + return NULL; + } + int32_t num_containers = (header >> 15); + + // typecodes, counts and keys + if (length < 4 + (size_t)num_containers * (1 + 2 + 2)) { + return NULL; + } + uint16_t *keys = (uint16_t *)(buf + length - 4 - num_containers * 5); + uint16_t *counts = (uint16_t *)(buf + length - 4 - num_containers * 3); + uint8_t *typecodes = (uint8_t *)(buf + length - 4 - num_containers * 1); + + // {bitset,array,run}_zone + int32_t num_bitset_containers = 0; + int32_t num_run_containers = 0; + int32_t num_array_containers = 0; + size_t bitset_zone_size = 0; + size_t run_zone_size = 0; + size_t array_zone_size = 0; + for (int32_t i = 0; i < num_containers; i++) { + switch (typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: + num_bitset_containers++; + bitset_zone_size += BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); + break; + case RUN_CONTAINER_TYPE_CODE: + num_run_containers++; + run_zone_size += counts[i] * sizeof(rle16_t); + break; + case ARRAY_CONTAINER_TYPE_CODE: + num_array_containers++; + array_zone_size += (counts[i] + UINT32_C(1)) * sizeof(uint16_t); + break; + default: + return NULL; + } + } + if (length != bitset_zone_size + run_zone_size + array_zone_size + + 5 * num_containers + 4) { + return NULL; + } + uint64_t *bitset_zone = (uint64_t*) (buf); + rle16_t *run_zone = (rle16_t*) (buf + bitset_zone_size); + uint16_t *array_zone = (uint16_t*) (buf + bitset_zone_size + run_zone_size); + + size_t alloc_size = 0; + alloc_size += sizeof(roaring_bitmap_t); + alloc_size += num_containers * sizeof(void *); + alloc_size += num_bitset_containers * sizeof(bitset_container_t); + alloc_size += num_run_containers * sizeof(run_container_t); + alloc_size += num_array_containers * sizeof(array_container_t); + + char *arena = (char *)malloc(alloc_size); + if (arena == NULL) { + return NULL; + } + + roaring_bitmap_t *rb = (roaring_bitmap_t *) + arena_alloc(&arena, sizeof(roaring_bitmap_t)); + rb->high_low_container.flags = ROARING_FLAG_FROZEN; + rb->high_low_container.allocation_size = num_containers; + rb->high_low_container.size = num_containers; + rb->high_low_container.keys = (uint16_t *)keys; + rb->high_low_container.typecodes = (uint8_t *)typecodes; + rb->high_low_container.containers = + (void **)arena_alloc(&arena, sizeof(void*) * num_containers); + for (int32_t i = 0; i < num_containers; i++) { + switch (typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: { + bitset_container_t *bitset = (bitset_container_t *) + arena_alloc(&arena, sizeof(bitset_container_t)); + bitset->array = bitset_zone; + bitset->cardinality = counts[i] + UINT32_C(1); + rb->high_low_container.containers[i] = bitset; + bitset_zone += BITSET_CONTAINER_SIZE_IN_WORDS; + break; + } + case RUN_CONTAINER_TYPE_CODE: { + run_container_t *run = (run_container_t *) + arena_alloc(&arena, sizeof(run_container_t)); + run->capacity = counts[i]; + run->n_runs = counts[i]; + run->runs = run_zone; + rb->high_low_container.containers[i] = run; + run_zone += run->n_runs; + break; + } + case ARRAY_CONTAINER_TYPE_CODE: { + array_container_t *array = (array_container_t *) + arena_alloc(&arena, sizeof(array_container_t)); + array->capacity = counts[i] + UINT32_C(1); + array->cardinality = counts[i] + UINT32_C(1); + array->array = array_zone; + rb->high_low_container.containers[i] = array; + array_zone += counts[i] + UINT32_C(1); + break; + } + default: + free(arena); + return NULL; + } + } + + return rb; +} +/* end file src/roaring.c */ +/* begin file src/roaring_array.c */ +#include +#include +#include +#include +#include +#include + + +// Convention: [0,ra->size) all elements are initialized +// [ra->size, ra->allocation_size) is junk and contains nothing needing freeing + +static bool realloc_array(roaring_array_t *ra, int32_t new_capacity) { + // because we combine the allocations, it is not possible to use realloc + /*ra->keys = + (uint16_t *)realloc(ra->keys, sizeof(uint16_t) * new_capacity); +ra->containers = + (void **)realloc(ra->containers, sizeof(void *) * new_capacity); +ra->typecodes = + (uint8_t *)realloc(ra->typecodes, sizeof(uint8_t) * new_capacity); +if (!ra->keys || !ra->containers || !ra->typecodes) { + free(ra->keys); + free(ra->containers); + free(ra->typecodes); + return false; +}*/ + + if ( new_capacity == 0 ) { + free(ra->containers); + ra->containers = NULL; + ra->keys = NULL; + ra->typecodes = NULL; + ra->allocation_size = 0; + return true; + } + const size_t memoryneeded = + new_capacity * (sizeof(uint16_t) + sizeof(void *) + sizeof(uint8_t)); + void *bigalloc = malloc(memoryneeded); + if (!bigalloc) return false; + void *oldbigalloc = ra->containers; + void **newcontainers = (void **)bigalloc; + uint16_t *newkeys = (uint16_t *)(newcontainers + new_capacity); + uint8_t *newtypecodes = (uint8_t *)(newkeys + new_capacity); + assert((char *)(newtypecodes + new_capacity) == + (char *)bigalloc + memoryneeded); + if(ra->size > 0) { + memcpy(newcontainers, ra->containers, sizeof(void *) * ra->size); + memcpy(newkeys, ra->keys, sizeof(uint16_t) * ra->size); + memcpy(newtypecodes, ra->typecodes, sizeof(uint8_t) * ra->size); + } + ra->containers = newcontainers; + ra->keys = newkeys; + ra->typecodes = newtypecodes; + ra->allocation_size = new_capacity; + free(oldbigalloc); + return true; +} + +bool ra_init_with_capacity(roaring_array_t *new_ra, uint32_t cap) { + if (!new_ra) return false; + ra_init(new_ra); + + if (cap > INT32_MAX) { return false; } + + if(cap > 0) { + void *bigalloc = + malloc(cap * (sizeof(uint16_t) + sizeof(void *) + sizeof(uint8_t))); + if( bigalloc == NULL ) return false; + new_ra->containers = (void **)bigalloc; + new_ra->keys = (uint16_t *)(new_ra->containers + cap); + new_ra->typecodes = (uint8_t *)(new_ra->keys + cap); + // Narrowing is safe because of above check + new_ra->allocation_size = (int32_t)cap; + } + return true; +} + +int ra_shrink_to_fit(roaring_array_t *ra) { + int savings = (ra->allocation_size - ra->size) * + (sizeof(uint16_t) + sizeof(void *) + sizeof(uint8_t)); + if (!realloc_array(ra, ra->size)) { + return 0; + } + ra->allocation_size = ra->size; + return savings; +} + +void ra_init(roaring_array_t *new_ra) { + if (!new_ra) { return; } + new_ra->keys = NULL; + new_ra->containers = NULL; + new_ra->typecodes = NULL; + + new_ra->allocation_size = 0; + new_ra->size = 0; + new_ra->flags = 0; +} + +bool ra_copy(const roaring_array_t *source, roaring_array_t *dest, + bool copy_on_write) { + if (!ra_init_with_capacity(dest, source->size)) return false; + dest->size = source->size; + dest->allocation_size = source->size; + if(dest->size > 0) { + memcpy(dest->keys, source->keys, dest->size * sizeof(uint16_t)); + } + // we go through the containers, turning them into shared containers... + if (copy_on_write) { + for (int32_t i = 0; i < dest->size; ++i) { + source->containers[i] = get_copy_of_container( + source->containers[i], &source->typecodes[i], copy_on_write); + } + // we do a shallow copy to the other bitmap + if(dest->size > 0) { + memcpy(dest->containers, source->containers, + dest->size * sizeof(void *)); + memcpy(dest->typecodes, source->typecodes, + dest->size * sizeof(uint8_t)); + } + } else { + if(dest->size > 0) { + memcpy(dest->typecodes, source->typecodes, + dest->size * sizeof(uint8_t)); + } + for (int32_t i = 0; i < dest->size; i++) { + dest->containers[i] = + container_clone(source->containers[i], source->typecodes[i]); + if (dest->containers[i] == NULL) { + for (int32_t j = 0; j < i; j++) { + container_free(dest->containers[j], dest->typecodes[j]); + } + ra_clear_without_containers(dest); + return false; + } + } + } + return true; +} + +bool ra_overwrite(const roaring_array_t *source, roaring_array_t *dest, + bool copy_on_write) { + ra_clear_containers(dest); // we are going to overwrite them + if (dest->allocation_size < source->size) { + if (!realloc_array(dest, source->size)) { + return false; + } + } + dest->size = source->size; + memcpy(dest->keys, source->keys, dest->size * sizeof(uint16_t)); + // we go through the containers, turning them into shared containers... + if (copy_on_write) { + for (int32_t i = 0; i < dest->size; ++i) { + source->containers[i] = get_copy_of_container( + source->containers[i], &source->typecodes[i], copy_on_write); + } + // we do a shallow copy to the other bitmap + memcpy(dest->containers, source->containers, + dest->size * sizeof(void *)); + memcpy(dest->typecodes, source->typecodes, + dest->size * sizeof(uint8_t)); + } else { + memcpy(dest->typecodes, source->typecodes, + dest->size * sizeof(uint8_t)); + for (int32_t i = 0; i < dest->size; i++) { + dest->containers[i] = + container_clone(source->containers[i], source->typecodes[i]); + if (dest->containers[i] == NULL) { + for (int32_t j = 0; j < i; j++) { + container_free(dest->containers[j], dest->typecodes[j]); + } + ra_clear_without_containers(dest); + return false; + } + } + } + return true; +} + +void ra_clear_containers(roaring_array_t *ra) { + for (int32_t i = 0; i < ra->size; ++i) { + container_free(ra->containers[i], ra->typecodes[i]); + } +} + +void ra_reset(roaring_array_t *ra) { + ra_clear_containers(ra); + ra->size = 0; + ra_shrink_to_fit(ra); +} + +void ra_clear_without_containers(roaring_array_t *ra) { + free(ra->containers); // keys and typecodes are allocated with containers + ra->size = 0; + ra->allocation_size = 0; + ra->containers = NULL; + ra->keys = NULL; + ra->typecodes = NULL; +} + +void ra_clear(roaring_array_t *ra) { + ra_clear_containers(ra); + ra_clear_without_containers(ra); +} + +bool extend_array(roaring_array_t *ra, int32_t k) { + int32_t desired_size = ra->size + k; + assert(desired_size <= MAX_CONTAINERS); + if (desired_size > ra->allocation_size) { + int32_t new_capacity = + (ra->size < 1024) ? 2 * desired_size : 5 * desired_size / 4; + if (new_capacity > MAX_CONTAINERS) { + new_capacity = MAX_CONTAINERS; + } + + return realloc_array(ra, new_capacity); + } + return true; +} + +void ra_append(roaring_array_t *ra, uint16_t key, void *container, + uint8_t typecode) { + extend_array(ra, 1); + const int32_t pos = ra->size; + + ra->keys[pos] = key; + ra->containers[pos] = container; + ra->typecodes[pos] = typecode; + ra->size++; +} + +void ra_append_copy(roaring_array_t *ra, const roaring_array_t *sa, + uint16_t index, bool copy_on_write) { + extend_array(ra, 1); + const int32_t pos = ra->size; + + // old contents is junk not needing freeing + ra->keys[pos] = sa->keys[index]; + // the shared container will be in two bitmaps + if (copy_on_write) { + sa->containers[index] = get_copy_of_container( + sa->containers[index], &sa->typecodes[index], copy_on_write); + ra->containers[pos] = sa->containers[index]; + ra->typecodes[pos] = sa->typecodes[index]; + } else { + ra->containers[pos] = + container_clone(sa->containers[index], sa->typecodes[index]); + ra->typecodes[pos] = sa->typecodes[index]; + } + ra->size++; +} + +void ra_append_copies_until(roaring_array_t *ra, const roaring_array_t *sa, + uint16_t stopping_key, bool copy_on_write) { + for (int32_t i = 0; i < sa->size; ++i) { + if (sa->keys[i] >= stopping_key) break; + ra_append_copy(ra, sa, i, copy_on_write); + } +} + +void ra_append_copy_range(roaring_array_t *ra, const roaring_array_t *sa, + int32_t start_index, int32_t end_index, + bool copy_on_write) { + extend_array(ra, end_index - start_index); + for (int32_t i = start_index; i < end_index; ++i) { + const int32_t pos = ra->size; + ra->keys[pos] = sa->keys[i]; + if (copy_on_write) { + sa->containers[i] = get_copy_of_container( + sa->containers[i], &sa->typecodes[i], copy_on_write); + ra->containers[pos] = sa->containers[i]; + ra->typecodes[pos] = sa->typecodes[i]; + } else { + ra->containers[pos] = + container_clone(sa->containers[i], sa->typecodes[i]); + ra->typecodes[pos] = sa->typecodes[i]; + } + ra->size++; + } +} + +void ra_append_copies_after(roaring_array_t *ra, const roaring_array_t *sa, + uint16_t before_start, bool copy_on_write) { + int start_location = ra_get_index(sa, before_start); + if (start_location >= 0) + ++start_location; + else + start_location = -start_location - 1; + ra_append_copy_range(ra, sa, start_location, sa->size, copy_on_write); +} + +void ra_append_move_range(roaring_array_t *ra, roaring_array_t *sa, + int32_t start_index, int32_t end_index) { + extend_array(ra, end_index - start_index); + + for (int32_t i = start_index; i < end_index; ++i) { + const int32_t pos = ra->size; + + ra->keys[pos] = sa->keys[i]; + ra->containers[pos] = sa->containers[i]; + ra->typecodes[pos] = sa->typecodes[i]; + ra->size++; + } +} + +void ra_append_range(roaring_array_t *ra, roaring_array_t *sa, + int32_t start_index, int32_t end_index, + bool copy_on_write) { + extend_array(ra, end_index - start_index); + + for (int32_t i = start_index; i < end_index; ++i) { + const int32_t pos = ra->size; + ra->keys[pos] = sa->keys[i]; + if (copy_on_write) { + sa->containers[i] = get_copy_of_container( + sa->containers[i], &sa->typecodes[i], copy_on_write); + ra->containers[pos] = sa->containers[i]; + ra->typecodes[pos] = sa->typecodes[i]; + } else { + ra->containers[pos] = + container_clone(sa->containers[i], sa->typecodes[i]); + ra->typecodes[pos] = sa->typecodes[i]; + } + ra->size++; + } +} + +uint16_t ra_get_key_at_index(const roaring_array_t *ra, uint16_t i) { + return ra->keys[i]; +} + +// everything skipped over is freed +int32_t ra_advance_until_freeing(roaring_array_t *ra, uint16_t x, int32_t pos) { + while (pos < ra->size && ra->keys[pos] < x) { + container_free(ra->containers[pos], ra->typecodes[pos]); + ++pos; + } + return pos; +} + +void ra_insert_new_key_value_at(roaring_array_t *ra, int32_t i, uint16_t key, + void *container, uint8_t typecode) { + extend_array(ra, 1); + // May be an optimization opportunity with DIY memmove + memmove(&(ra->keys[i + 1]), &(ra->keys[i]), + sizeof(uint16_t) * (ra->size - i)); + memmove(&(ra->containers[i + 1]), &(ra->containers[i]), + sizeof(void *) * (ra->size - i)); + memmove(&(ra->typecodes[i + 1]), &(ra->typecodes[i]), + sizeof(uint8_t) * (ra->size - i)); + ra->keys[i] = key; + ra->containers[i] = container; + ra->typecodes[i] = typecode; + ra->size++; +} + +// note: Java routine set things to 0, enabling GC. +// Java called it "resize" but it was always used to downsize. +// Allowing upsize would break the conventions about +// valid containers below ra->size. + +void ra_downsize(roaring_array_t *ra, int32_t new_length) { + assert(new_length <= ra->size); + ra->size = new_length; +} + +void ra_remove_at_index(roaring_array_t *ra, int32_t i) { + memmove(&(ra->containers[i]), &(ra->containers[i + 1]), + sizeof(void *) * (ra->size - i - 1)); + memmove(&(ra->keys[i]), &(ra->keys[i + 1]), + sizeof(uint16_t) * (ra->size - i - 1)); + memmove(&(ra->typecodes[i]), &(ra->typecodes[i + 1]), + sizeof(uint8_t) * (ra->size - i - 1)); + ra->size--; +} + +void ra_remove_at_index_and_free(roaring_array_t *ra, int32_t i) { + container_free(ra->containers[i], ra->typecodes[i]); + ra_remove_at_index(ra, i); +} + +// used in inplace andNot only, to slide left the containers from +// the mutated RoaringBitmap that are after the largest container of +// the argument RoaringBitmap. In use it should be followed by a call to +// downsize. +// +void ra_copy_range(roaring_array_t *ra, uint32_t begin, uint32_t end, + uint32_t new_begin) { + assert(begin <= end); + assert(new_begin < begin); + + const int range = end - begin; + + // We ensure to previously have freed overwritten containers + // that are not copied elsewhere + + memmove(&(ra->containers[new_begin]), &(ra->containers[begin]), + sizeof(void *) * range); + memmove(&(ra->keys[new_begin]), &(ra->keys[begin]), + sizeof(uint16_t) * range); + memmove(&(ra->typecodes[new_begin]), &(ra->typecodes[begin]), + sizeof(uint8_t) * range); +} + +void ra_shift_tail(roaring_array_t *ra, int32_t count, int32_t distance) { + if (distance > 0) { + extend_array(ra, distance); + } + int32_t srcpos = ra->size - count; + int32_t dstpos = srcpos + distance; + memmove(&(ra->keys[dstpos]), &(ra->keys[srcpos]), + sizeof(uint16_t) * count); + memmove(&(ra->containers[dstpos]), &(ra->containers[srcpos]), + sizeof(void *) * count); + memmove(&(ra->typecodes[dstpos]), &(ra->typecodes[srcpos]), + sizeof(uint8_t) * count); + ra->size += distance; +} + + +void ra_to_uint32_array(const roaring_array_t *ra, uint32_t *ans) { + size_t ctr = 0; + for (int32_t i = 0; i < ra->size; ++i) { + int num_added = container_to_uint32_array( + ans + ctr, ra->containers[i], ra->typecodes[i], + ((uint32_t)ra->keys[i]) << 16); + ctr += num_added; + } +} + +bool ra_range_uint32_array(const roaring_array_t *ra, size_t offset, size_t limit, uint32_t *ans) { + size_t ctr = 0; + size_t dtr = 0; + + size_t t_limit = 0; + + bool first = false; + size_t first_skip = 0; + + uint32_t *t_ans = NULL; + size_t cur_len = 0; + + for (int i = 0; i < ra->size; ++i) { + + const void *container = container_unwrap_shared(ra->containers[i], &ra->typecodes[i]); + switch (ra->typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: + t_limit = ((const bitset_container_t *)container)->cardinality; + break; + case ARRAY_CONTAINER_TYPE_CODE: + t_limit = ((const array_container_t *)container)->cardinality; + break; + case RUN_CONTAINER_TYPE_CODE: + t_limit = run_container_cardinality((const run_container_t *)container); + break; + case SHARED_CONTAINER_TYPE_CODE: + default: + __builtin_unreachable(); + } + if (ctr + t_limit - 1 >= offset && ctr < offset + limit){ + if (!first){ + //first_skip = t_limit - (ctr + t_limit - offset); + first_skip = offset - ctr; + first = true; + t_ans = (uint32_t *)malloc(sizeof(*t_ans) * (first_skip + limit)); + if(t_ans == NULL) { + return false; + } + memset(t_ans, 0, sizeof(*t_ans) * (first_skip + limit)) ; + cur_len = first_skip + limit; + } + if (dtr + t_limit > cur_len){ + uint32_t * append_ans = (uint32_t *)malloc(sizeof(*append_ans) * (cur_len + t_limit)); + if(append_ans == NULL) { + if(t_ans != NULL) free(t_ans); + return false; + } + memset(append_ans, 0, sizeof(*append_ans) * (cur_len + t_limit)); + cur_len = cur_len + t_limit; + memcpy(append_ans, t_ans, dtr * sizeof(uint32_t)); + free(t_ans); + t_ans = append_ans; + } + switch (ra->typecodes[i]) { + case BITSET_CONTAINER_TYPE_CODE: + container_to_uint32_array( + t_ans + dtr, (const bitset_container_t *)container, ra->typecodes[i], + ((uint32_t)ra->keys[i]) << 16); + break; + case ARRAY_CONTAINER_TYPE_CODE: + container_to_uint32_array( + t_ans + dtr, (const array_container_t *)container, ra->typecodes[i], + ((uint32_t)ra->keys[i]) << 16); + break; + case RUN_CONTAINER_TYPE_CODE: + container_to_uint32_array( + t_ans + dtr, (const run_container_t *)container, ra->typecodes[i], + ((uint32_t)ra->keys[i]) << 16); + break; + case SHARED_CONTAINER_TYPE_CODE: + default: + __builtin_unreachable(); + } + dtr += t_limit; + } + ctr += t_limit; + if (dtr-first_skip >= limit) break; + } + if(t_ans != NULL) { + memcpy(ans, t_ans+first_skip, limit * sizeof(uint32_t)); + free(t_ans); + } + return true; +} + +bool ra_has_run_container(const roaring_array_t *ra) { + for (int32_t k = 0; k < ra->size; ++k) { + if (get_container_type(ra->containers[k], ra->typecodes[k]) == + RUN_CONTAINER_TYPE_CODE) + return true; + } + return false; +} + +uint32_t ra_portable_header_size(const roaring_array_t *ra) { + if (ra_has_run_container(ra)) { + if (ra->size < + NO_OFFSET_THRESHOLD) { // for small bitmaps, we omit the offsets + return 4 + (ra->size + 7) / 8 + 4 * ra->size; + } + return 4 + (ra->size + 7) / 8 + + 8 * ra->size; // - 4 because we pack the size with the cookie + } else { + return 4 + 4 + 8 * ra->size; + } +} + +size_t ra_portable_size_in_bytes(const roaring_array_t *ra) { + size_t count = ra_portable_header_size(ra); + + for (int32_t k = 0; k < ra->size; ++k) { + count += container_size_in_bytes(ra->containers[k], ra->typecodes[k]); + } + return count; +} + +size_t ra_portable_serialize(const roaring_array_t *ra, char *buf) { + char *initbuf = buf; + uint32_t startOffset = 0; + bool hasrun = ra_has_run_container(ra); + if (hasrun) { + uint32_t cookie = SERIAL_COOKIE | ((ra->size - 1) << 16); + memcpy(buf, &cookie, sizeof(cookie)); + buf += sizeof(cookie); + uint32_t s = (ra->size + 7) / 8; + uint8_t *bitmapOfRunContainers = (uint8_t *)calloc(s, 1); + assert(bitmapOfRunContainers != NULL); // todo: handle + for (int32_t i = 0; i < ra->size; ++i) { + if (get_container_type(ra->containers[i], ra->typecodes[i]) == + RUN_CONTAINER_TYPE_CODE) { + bitmapOfRunContainers[i / 8] |= (1 << (i % 8)); + } + } + memcpy(buf, bitmapOfRunContainers, s); + buf += s; + free(bitmapOfRunContainers); + if (ra->size < NO_OFFSET_THRESHOLD) { + startOffset = 4 + 4 * ra->size + s; + } else { + startOffset = 4 + 8 * ra->size + s; + } + } else { // backwards compatibility + uint32_t cookie = SERIAL_COOKIE_NO_RUNCONTAINER; + + memcpy(buf, &cookie, sizeof(cookie)); + buf += sizeof(cookie); + memcpy(buf, &ra->size, sizeof(ra->size)); + buf += sizeof(ra->size); + + startOffset = 4 + 4 + 4 * ra->size + 4 * ra->size; + } + for (int32_t k = 0; k < ra->size; ++k) { + memcpy(buf, &ra->keys[k], sizeof(ra->keys[k])); + buf += sizeof(ra->keys[k]); + // get_cardinality returns a value in [1,1<<16], subtracting one + // we get [0,1<<16 - 1] which fits in 16 bits + uint16_t card = (uint16_t)( + container_get_cardinality(ra->containers[k], ra->typecodes[k]) - 1); + memcpy(buf, &card, sizeof(card)); + buf += sizeof(card); + } + if ((!hasrun) || (ra->size >= NO_OFFSET_THRESHOLD)) { + // writing the containers offsets + for (int32_t k = 0; k < ra->size; k++) { + memcpy(buf, &startOffset, sizeof(startOffset)); + buf += sizeof(startOffset); + startOffset = + startOffset + + container_size_in_bytes(ra->containers[k], ra->typecodes[k]); + } + } + for (int32_t k = 0; k < ra->size; ++k) { + buf += container_write(ra->containers[k], ra->typecodes[k], buf); + } + return buf - initbuf; +} + +// Quickly checks whether there is a serialized bitmap at the pointer, +// not exceeding size "maxbytes" in bytes. This function does not allocate +// memory dynamically. +// +// This function returns 0 if and only if no valid bitmap is found. +// Otherwise, it returns how many bytes are occupied. +// +size_t ra_portable_deserialize_size(const char *buf, const size_t maxbytes) { + size_t bytestotal = sizeof(int32_t);// for cookie + if(bytestotal > maxbytes) return 0; + uint32_t cookie; + memcpy(&cookie, buf, sizeof(int32_t)); + buf += sizeof(uint32_t); + if ((cookie & 0xFFFF) != SERIAL_COOKIE && + cookie != SERIAL_COOKIE_NO_RUNCONTAINER) { + return 0; + } + int32_t size; + + if ((cookie & 0xFFFF) == SERIAL_COOKIE) + size = (cookie >> 16) + 1; + else { + bytestotal += sizeof(int32_t); + if(bytestotal > maxbytes) return 0; + memcpy(&size, buf, sizeof(int32_t)); + buf += sizeof(uint32_t); + } + if (size > (1<<16)) { + return 0; // logically impossible + } + char *bitmapOfRunContainers = NULL; + bool hasrun = (cookie & 0xFFFF) == SERIAL_COOKIE; + if (hasrun) { + int32_t s = (size + 7) / 8; + bytestotal += s; + if(bytestotal > maxbytes) return 0; + bitmapOfRunContainers = (char *)buf; + buf += s; + } + bytestotal += size * 2 * sizeof(uint16_t); + if(bytestotal > maxbytes) return 0; + uint16_t *keyscards = (uint16_t *)buf; + buf += size * 2 * sizeof(uint16_t); + if ((!hasrun) || (size >= NO_OFFSET_THRESHOLD)) { + // skipping the offsets + bytestotal += size * 4; + if(bytestotal > maxbytes) return 0; + buf += size * 4; + } + // Reading the containers + for (int32_t k = 0; k < size; ++k) { + uint16_t tmp; + memcpy(&tmp, keyscards + 2*k+1, sizeof(tmp)); + uint32_t thiscard = tmp + 1; + bool isbitmap = (thiscard > DEFAULT_MAX_SIZE); + bool isrun = false; + if(hasrun) { + if((bitmapOfRunContainers[k / 8] & (1 << (k % 8))) != 0) { + isbitmap = false; + isrun = true; + } + } + if (isbitmap) { + size_t containersize = BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); + bytestotal += containersize; + if(bytestotal > maxbytes) return 0; + buf += containersize; + } else if (isrun) { + bytestotal += sizeof(uint16_t); + if(bytestotal > maxbytes) return 0; + uint16_t n_runs; + memcpy(&n_runs, buf, sizeof(uint16_t)); + buf += sizeof(uint16_t); + size_t containersize = n_runs * sizeof(rle16_t); + bytestotal += containersize; + if(bytestotal > maxbytes) return 0; + buf += containersize; + } else { + size_t containersize = thiscard * sizeof(uint16_t); + bytestotal += containersize; + if(bytestotal > maxbytes) return 0; + buf += containersize; + } + } + return bytestotal; +} + + +// this function populates answer from the content of buf (reading up to maxbytes bytes). +// The function returns false if a properly serialized bitmap cannot be found. +// if it returns true, readbytes is populated by how many bytes were read, we have that *readbytes <= maxbytes. +bool ra_portable_deserialize(roaring_array_t *answer, const char *buf, const size_t maxbytes, size_t * readbytes) { + *readbytes = sizeof(int32_t);// for cookie + if(*readbytes > maxbytes) { + fprintf(stderr, "Ran out of bytes while reading first 4 bytes.\n"); + return false; + } + uint32_t cookie; + memcpy(&cookie, buf, sizeof(int32_t)); + buf += sizeof(uint32_t); + if ((cookie & 0xFFFF) != SERIAL_COOKIE && + cookie != SERIAL_COOKIE_NO_RUNCONTAINER) { + fprintf(stderr, "I failed to find one of the right cookies. Found %" PRIu32 "\n", + cookie); + return false; + } + int32_t size; + + if ((cookie & 0xFFFF) == SERIAL_COOKIE) + size = (cookie >> 16) + 1; + else { + *readbytes += sizeof(int32_t); + if(*readbytes > maxbytes) { + fprintf(stderr, "Ran out of bytes while reading second part of the cookie.\n"); + return false; + } + memcpy(&size, buf, sizeof(int32_t)); + buf += sizeof(uint32_t); + } + if (size > (1<<16)) { + fprintf(stderr, "You cannot have so many containers, the data must be corrupted: %" PRId32 "\n", + size); + return false; // logically impossible + } + const char *bitmapOfRunContainers = NULL; + bool hasrun = (cookie & 0xFFFF) == SERIAL_COOKIE; + if (hasrun) { + int32_t s = (size + 7) / 8; + *readbytes += s; + if(*readbytes > maxbytes) {// data is corrupted? + fprintf(stderr, "Ran out of bytes while reading run bitmap.\n"); + return false; + } + bitmapOfRunContainers = buf; + buf += s; + } + uint16_t *keyscards = (uint16_t *)buf; + + *readbytes += size * 2 * sizeof(uint16_t); + if(*readbytes > maxbytes) { + fprintf(stderr, "Ran out of bytes while reading key-cardinality array.\n"); + return false; + } + buf += size * 2 * sizeof(uint16_t); + + bool is_ok = ra_init_with_capacity(answer, size); + if (!is_ok) { + fprintf(stderr, "Failed to allocate memory for roaring array. Bailing out.\n"); + return false; + } + + for (int32_t k = 0; k < size; ++k) { + uint16_t tmp; + memcpy(&tmp, keyscards + 2*k, sizeof(tmp)); + answer->keys[k] = tmp; + } + if ((!hasrun) || (size >= NO_OFFSET_THRESHOLD)) { + *readbytes += size * 4; + if(*readbytes > maxbytes) {// data is corrupted? + fprintf(stderr, "Ran out of bytes while reading offsets.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + + // skipping the offsets + buf += size * 4; + } + // Reading the containers + for (int32_t k = 0; k < size; ++k) { + uint16_t tmp; + memcpy(&tmp, keyscards + 2*k+1, sizeof(tmp)); + uint32_t thiscard = tmp + 1; + bool isbitmap = (thiscard > DEFAULT_MAX_SIZE); + bool isrun = false; + if(hasrun) { + if((bitmapOfRunContainers[k / 8] & (1 << (k % 8))) != 0) { + isbitmap = false; + isrun = true; + } + } + if (isbitmap) { + // we check that the read is allowed + size_t containersize = BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); + *readbytes += containersize; + if(*readbytes > maxbytes) { + fprintf(stderr, "Running out of bytes while reading a bitset container.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + // it is now safe to read + bitset_container_t *c = bitset_container_create(); + if(c == NULL) {// memory allocation failure + fprintf(stderr, "Failed to allocate memory for a bitset container.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + answer->size++; + buf += bitset_container_read(thiscard, c, buf); + answer->containers[k] = c; + answer->typecodes[k] = BITSET_CONTAINER_TYPE_CODE; + } else if (isrun) { + // we check that the read is allowed + *readbytes += sizeof(uint16_t); + if(*readbytes > maxbytes) { + fprintf(stderr, "Running out of bytes while reading a run container (header).\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + uint16_t n_runs; + memcpy(&n_runs, buf, sizeof(uint16_t)); + size_t containersize = n_runs * sizeof(rle16_t); + *readbytes += containersize; + if(*readbytes > maxbytes) {// data is corrupted? + fprintf(stderr, "Running out of bytes while reading a run container.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + // it is now safe to read + + run_container_t *c = run_container_create(); + if(c == NULL) {// memory allocation failure + fprintf(stderr, "Failed to allocate memory for a run container.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + answer->size++; + buf += run_container_read(thiscard, c, buf); + answer->containers[k] = c; + answer->typecodes[k] = RUN_CONTAINER_TYPE_CODE; + } else { + // we check that the read is allowed + size_t containersize = thiscard * sizeof(uint16_t); + *readbytes += containersize; + if(*readbytes > maxbytes) {// data is corrupted? + fprintf(stderr, "Running out of bytes while reading an array container.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + // it is now safe to read + array_container_t *c = + array_container_create_given_capacity(thiscard); + if(c == NULL) {// memory allocation failure + fprintf(stderr, "Failed to allocate memory for an array container.\n"); + ra_clear(answer);// we need to clear the containers already allocated, and the roaring array + return false; + } + answer->size++; + buf += array_container_read(thiscard, c, buf); + answer->containers[k] = c; + answer->typecodes[k] = ARRAY_CONTAINER_TYPE_CODE; + } + } + return true; +} +/* end file src/roaring_array.c */ +/* begin file src/roaring_priority_queue.c */ + +struct roaring_pq_element_s { + uint64_t size; + bool is_temporary; + roaring_bitmap_t *bitmap; +}; + +typedef struct roaring_pq_element_s roaring_pq_element_t; + +struct roaring_pq_s { + roaring_pq_element_t *elements; + uint64_t size; +}; + +typedef struct roaring_pq_s roaring_pq_t; + +static inline bool compare(roaring_pq_element_t *t1, roaring_pq_element_t *t2) { + return t1->size < t2->size; +} + +static void pq_add(roaring_pq_t *pq, roaring_pq_element_t *t) { + uint64_t i = pq->size; + pq->elements[pq->size++] = *t; + while (i > 0) { + uint64_t p = (i - 1) >> 1; + roaring_pq_element_t ap = pq->elements[p]; + if (!compare(t, &ap)) break; + pq->elements[i] = ap; + i = p; + } + pq->elements[i] = *t; +} + +static void pq_free(roaring_pq_t *pq) { + free(pq->elements); + pq->elements = NULL; // paranoid + free(pq); +} + +static void percolate_down(roaring_pq_t *pq, uint32_t i) { + uint32_t size = (uint32_t)pq->size; + uint32_t hsize = size >> 1; + roaring_pq_element_t ai = pq->elements[i]; + while (i < hsize) { + uint32_t l = (i << 1) + 1; + uint32_t r = l + 1; + roaring_pq_element_t bestc = pq->elements[l]; + if (r < size) { + if (compare(pq->elements + r, &bestc)) { + l = r; + bestc = pq->elements[r]; + } + } + if (!compare(&bestc, &ai)) { + break; + } + pq->elements[i] = bestc; + i = l; + } + pq->elements[i] = ai; +} + +static roaring_pq_t *create_pq(const roaring_bitmap_t **arr, uint32_t length) { + roaring_pq_t *answer = (roaring_pq_t *)malloc(sizeof(roaring_pq_t)); + answer->elements = + (roaring_pq_element_t *)malloc(sizeof(roaring_pq_element_t) * length); + answer->size = length; + for (uint32_t i = 0; i < length; i++) { + answer->elements[i].bitmap = (roaring_bitmap_t *)arr[i]; + answer->elements[i].is_temporary = false; + answer->elements[i].size = + roaring_bitmap_portable_size_in_bytes(arr[i]); + } + for (int32_t i = (length >> 1); i >= 0; i--) { + percolate_down(answer, i); + } + return answer; +} + +static roaring_pq_element_t pq_poll(roaring_pq_t *pq) { + roaring_pq_element_t ans = *pq->elements; + if (pq->size > 1) { + pq->elements[0] = pq->elements[--pq->size]; + percolate_down(pq, 0); + } else + --pq->size; + // memmove(pq->elements,pq->elements+1,(pq->size-1)*sizeof(roaring_pq_element_t));--pq->size; + return ans; +} + +// this function consumes and frees the inputs +static roaring_bitmap_t *lazy_or_from_lazy_inputs(roaring_bitmap_t *x1, + roaring_bitmap_t *x2) { + uint8_t container_result_type = 0; + const int length1 = ra_get_size(&x1->high_low_container), + length2 = ra_get_size(&x2->high_low_container); + if (0 == length1) { + roaring_bitmap_free(x1); + return x2; + } + if (0 == length2) { + roaring_bitmap_free(x2); + return x1; + } + uint32_t neededcap = length1 > length2 ? length2 : length1; + roaring_bitmap_t *answer = roaring_bitmap_create_with_capacity(neededcap); + int pos1 = 0, pos2 = 0; + uint8_t container_type_1, container_type_2; + uint16_t s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + uint16_t s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + while (true) { + if (s1 == s2) { + // todo: unsharing can be inefficient as it may create a clone where + // none + // is needed, but it has the benefit of being easy to reason about. + ra_unshare_container_at_index(&x1->high_low_container, pos1); + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + assert(container_type_1 != SHARED_CONTAINER_TYPE_CODE); + ra_unshare_container_at_index(&x2->high_low_container, pos2); + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + assert(container_type_2 != SHARED_CONTAINER_TYPE_CODE); + void *c; + + if ((container_type_2 == BITSET_CONTAINER_TYPE_CODE) && + (container_type_1 != BITSET_CONTAINER_TYPE_CODE)) { + c = container_lazy_ior(c2, container_type_2, c1, + container_type_1, + &container_result_type); + container_free(c1, container_type_1); + if (c != c2) { + container_free(c2, container_type_2); + } + } else { + c = container_lazy_ior(c1, container_type_1, c2, + container_type_2, + &container_result_type); + container_free(c2, container_type_2); + if (c != c1) { + container_free(c1, container_type_1); + } + } + // since we assume that the initial containers are non-empty, the + // result here + // can only be non-empty + ra_append(&answer->high_low_container, s1, c, + container_result_type); + ++pos1; + ++pos2; + if (pos1 == length1) break; + if (pos2 == length2) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + + } else if (s1 < s2) { // s1 < s2 + void *c1 = ra_get_container_at_index(&x1->high_low_container, pos1, + &container_type_1); + ra_append(&answer->high_low_container, s1, c1, container_type_1); + pos1++; + if (pos1 == length1) break; + s1 = ra_get_key_at_index(&x1->high_low_container, pos1); + + } else { // s1 > s2 + void *c2 = ra_get_container_at_index(&x2->high_low_container, pos2, + &container_type_2); + ra_append(&answer->high_low_container, s2, c2, container_type_2); + pos2++; + if (pos2 == length2) break; + s2 = ra_get_key_at_index(&x2->high_low_container, pos2); + } + } + if (pos1 == length1) { + ra_append_move_range(&answer->high_low_container, + &x2->high_low_container, pos2, length2); + } else if (pos2 == length2) { + ra_append_move_range(&answer->high_low_container, + &x1->high_low_container, pos1, length1); + } + ra_clear_without_containers(&x1->high_low_container); + ra_clear_without_containers(&x2->high_low_container); + free(x1); + free(x2); + return answer; +} + +/** + * Compute the union of 'number' bitmaps using a heap. This can + * sometimes be faster than roaring_bitmap_or_many which uses + * a naive algorithm. Caller is responsible for freeing the + * result. + */ +roaring_bitmap_t *roaring_bitmap_or_many_heap(uint32_t number, + const roaring_bitmap_t **x) { + if (number == 0) { + return roaring_bitmap_create(); + } + if (number == 1) { + return roaring_bitmap_copy(x[0]); + } + roaring_pq_t *pq = create_pq(x, number); + while (pq->size > 1) { + roaring_pq_element_t x1 = pq_poll(pq); + roaring_pq_element_t x2 = pq_poll(pq); + + if (x1.is_temporary && x2.is_temporary) { + roaring_bitmap_t *newb = + lazy_or_from_lazy_inputs(x1.bitmap, x2.bitmap); + // should normally return a fresh new bitmap *except* that + // it can return x1.bitmap or x2.bitmap in degenerate cases + bool temporary = !((newb == x1.bitmap) && (newb == x2.bitmap)); + uint64_t bsize = roaring_bitmap_portable_size_in_bytes(newb); + roaring_pq_element_t newelement = { + .size = bsize, .is_temporary = temporary, .bitmap = newb}; + pq_add(pq, &newelement); + } else if (x2.is_temporary) { + roaring_bitmap_lazy_or_inplace(x2.bitmap, x1.bitmap, false); + x2.size = roaring_bitmap_portable_size_in_bytes(x2.bitmap); + pq_add(pq, &x2); + } else if (x1.is_temporary) { + roaring_bitmap_lazy_or_inplace(x1.bitmap, x2.bitmap, false); + x1.size = roaring_bitmap_portable_size_in_bytes(x1.bitmap); + + pq_add(pq, &x1); + } else { + roaring_bitmap_t *newb = + roaring_bitmap_lazy_or(x1.bitmap, x2.bitmap, false); + uint64_t bsize = roaring_bitmap_portable_size_in_bytes(newb); + roaring_pq_element_t newelement = { + .size = bsize, .is_temporary = true, .bitmap = newb}; + + pq_add(pq, &newelement); + } + } + roaring_pq_element_t X = pq_poll(pq); + roaring_bitmap_t *answer = X.bitmap; + roaring_bitmap_repair_after_lazy(answer); + pq_free(pq); + return answer; +} +/* end file src/roaring_priority_queue.c */ + +#pragma GCC diagnostic pop diff --git a/contrib/eggbitset/roaring.h b/contrib/eggbitset/roaring.h new file mode 100644 index 00000000..1b5a5937 --- /dev/null +++ b/contrib/eggbitset/roaring.h @@ -0,0 +1,7090 @@ +/* + * Amalgamated copy of CRoaring 0.2.66, modified for GTK to reduce compiler + * warnings. + * + * Copyright 2016-2020 The CRoaring authors + * Copyright 2020 Benjamin Otte + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* begin file include/roaring/roaring_version.h */ +// /include/roaring/roaring_version.h automatically generated by release.py, do not change by hand +#ifndef ROARING_INCLUDE_ROARING_VERSION +#define ROARING_INCLUDE_ROARING_VERSION +#define ROARING_VERSION = 0.2.66, +enum { + ROARING_VERSION_MAJOR = 0, + ROARING_VERSION_MINOR = 2, + ROARING_VERSION_REVISION = 66 +}; +#endif // ROARING_INCLUDE_ROARING_VERSION +/* end file include/roaring/roaring_version.h */ +/* begin file include/roaring/portability.h */ +/* + * portability.h + * + */ + +#ifndef INCLUDE_PORTABILITY_H_ +#define INCLUDE_PORTABILITY_H_ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS 1 +#endif + +#if !(defined(_POSIX_C_SOURCE)) || (_POSIX_C_SOURCE < 200809L) +#define _POSIX_C_SOURCE 200809L +#endif +#if !(defined(_XOPEN_SOURCE)) || (_XOPEN_SOURCE < 700) +#define _XOPEN_SOURCE 700 +#endif + +#include +#include +#include // will provide posix_memalign with _POSIX_C_SOURCE as defined above +#if !(defined(__APPLE__)) && !(defined(__FreeBSD__)) && !(defined(__OpenBSD__)) +#include // this should never be needed but there are some reports that it is needed. +#endif + + +#if defined(_MSC_VER) && !defined(__clang__) && !defined(_WIN64) && !defined(ROARING_ACK_32BIT) +#pragma message( \ + "You appear to be attempting a 32-bit build under Visual Studio. We recommend a 64-bit build instead.") +#endif + +#if defined(__SIZEOF_LONG_LONG__) && __SIZEOF_LONG_LONG__ != 8 +#error This code assumes 64-bit long longs (by use of the GCC intrinsics). Your system is not currently supported. +#endif + +#if defined(_MSC_VER) +#define __restrict__ __restrict +#endif + +#ifndef DISABLE_X64 // some users may want to compile as if they did not have + // an x64 processor + +/////////////////////// +/// We support X64 hardware in the following manner: +/// +/// if IS_X64 is defined then we have at least SSE and SSE2 +/// (All Intel processors sold in the recent past have at least SSE and SSE2 support, +/// going back to the Pentium 4.) +/// +/// if USESSE4 is defined then we assume at least SSE4.2, SSE4.1, +/// SSSE3, SSE3... + IS_X64 +/// if USEAVX is defined, then we assume AVX2, AVX + USESSE4 +/// +/// So if you have hardware that supports AVX but not AVX2, then "USEAVX" +/// won't be enabled. +/// If you have hardware that supports SSE4.1, but not SSE4.2, then USESSE4 +/// won't be defined. +////////////////////// + +// unless DISABLEAVX was defined, if we have __AVX2__, we enable AVX +#if (!defined(USEAVX)) && (!defined(DISABLEAVX)) && (defined(__AVX2__)) +#define USEAVX +#endif + +// if we have __SSE4_2__, we enable SSE4 +#if (defined(__POPCNT__)) && (defined(__SSE4_2__)) +#define USESSE4 +#endif + +#if defined(USEAVX) || defined(__x86_64__) || defined(_M_X64) +// we have an x64 processor +#define IS_X64 +// we include the intrinsic header +#ifndef _MSC_VER +/* Non-Microsoft C/C++-compatible compiler */ +#include // on some recent GCC, this will declare posix_memalign +#endif +#endif + +#if !defined(USENEON) && !defined(DISABLENEON) && defined(__ARM_NEON) +# define USENEON +#endif +#if defined(USENEON) +# include +#endif + +#ifndef _MSC_VER +/* Non-Microsoft C/C++-compatible compiler, assumes that it supports inline + * assembly */ +#define ROARING_INLINE_ASM +#endif + +#ifdef USEAVX +#define USESSE4 // if we have AVX, then we have SSE4 +#define USE_BMI // we assume that AVX2 and BMI go hand and hand +#define USEAVX2FORDECODING // optimization +// vector operations should work on not just AVX +#define ROARING_VECTOR_OPERATIONS_ENABLED // vector unions (optimization) +#endif + +#endif // DISABLE_X64 + +#ifdef _MSC_VER +/* Microsoft C/C++-compatible compiler */ +#include + +#ifndef __clang__ // if one compiles with MSVC *with* clang, then these + // intrinsics are defined!!! +// sadly there is no way to check whether we are missing these intrinsics +// specifically. + +/* wrappers for Visual Studio built-ins that look like gcc built-ins */ +/* result might be undefined when input_num is zero */ +static inline int __builtin_ctzll(unsigned long long input_num) { + unsigned long index; +#ifdef _WIN64 // highly recommended!!! + _BitScanForward64(&index, input_num); +#else // if we must support 32-bit Windows + if ((uint32_t)input_num != 0) { + _BitScanForward(&index, (uint32_t)input_num); + } else { + _BitScanForward(&index, (uint32_t)(input_num >> 32)); + index += 32; + } +#endif + return index; +} + +/* result might be undefined when input_num is zero */ +static inline int __builtin_clzll(unsigned long long input_num) { + unsigned long index; +#ifdef _WIN64 // highly recommended!!! + _BitScanReverse64(&index, input_num); +#else // if we must support 32-bit Windows + if (input_num > 0xFFFFFFFF) { + _BitScanReverse(&index, (uint32_t)(input_num >> 32)); + index += 32; + } else { + _BitScanReverse(&index, (uint32_t)(input_num)); + } +#endif + return 63 - index; +} + +/* result might be undefined when input_num is zero */ +#ifdef USESSE4 +/* POPCNT support was added to processors around the release of SSE4.2 */ +/* USESSE4 flag guarantees POPCNT support */ +static inline int __builtin_popcountll(unsigned long long input_num) { +#ifdef _WIN64 // highly recommended!!! + return (int)__popcnt64(input_num); +#else // if we must support 32-bit Windows + return (int)(__popcnt((uint32_t)input_num) + + __popcnt((uint32_t)(input_num >> 32))); +#endif +} +#else +/* software implementation avoids POPCNT */ +static inline int __builtin_popcountll(unsigned long long input_num) { + const uint64_t m1 = 0x5555555555555555; //binary: 0101... + const uint64_t m2 = 0x3333333333333333; //binary: 00110011.. + const uint64_t m4 = 0x0f0f0f0f0f0f0f0f; //binary: 4 zeros, 4 ones ... + const uint64_t h01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3... + + input_num -= (input_num >> 1) & m1; + input_num = (input_num & m2) + ((input_num >> 2) & m2); + input_num = (input_num + (input_num >> 4)) & m4; + return (input_num * h01) >> 56; +} +#endif + +/* Use #define so this is effective even under /Ob0 (no inline) */ +#define __builtin_unreachable() __assume(0) +#endif + +#endif + +// portable version of posix_memalign +static inline void *roaring_bitmap_aligned_malloc(size_t alignment, size_t size) { + void *p; +#ifdef _MSC_VER + p = _aligned_malloc(size, alignment); +#elif defined(__MINGW32__) || defined(__MINGW64__) + p = __mingw_aligned_malloc(size, alignment); +#else + // somehow, if this is used before including "x86intrin.h", it creates an + // implicit defined warning. + if (posix_memalign(&p, alignment, size) != 0) return NULL; +#endif + return p; +} + +static inline void roaring_bitmap_aligned_free(void *memblock) { +#ifdef _MSC_VER + _aligned_free(memblock); +#elif defined(__MINGW32__) || defined(__MINGW64__) + __mingw_aligned_free(memblock); +#else + free(memblock); +#endif +} + +#if defined(_MSC_VER) +#define ALIGNED(x) __declspec(align(x)) +#else +#if defined(__GNUC__) +#define ALIGNED(x) __attribute__((aligned(x))) +#endif +#endif + +#ifdef __GNUC__ +#define WARN_UNUSED __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED +#endif + +#define IS_BIG_ENDIAN (*(uint16_t *)"\0\xff" < 0x100) + +static inline int hamming(uint64_t x) { +#ifdef USESSE4 + return (int) _mm_popcnt_u64(x); +#else + // won't work under visual studio, but hopeful we have _mm_popcnt_u64 in + // many cases + return __builtin_popcountll(x); +#endif +} + +#ifndef UINT64_C +#define UINT64_C(c) (c##ULL) +#endif + +#ifndef UINT32_C +#define UINT32_C(c) (c##UL) +#endif + +#endif /* INCLUDE_PORTABILITY_H_ */ +/* end file include/roaring/portability.h */ +/* begin file include/roaring/containers/perfparameters.h */ +#ifndef PERFPARAMETERS_H_ +#define PERFPARAMETERS_H_ + +#include + +/** +During lazy computations, we can transform array containers into bitset +containers as +long as we can expect them to have ARRAY_LAZY_LOWERBOUND values. +*/ +enum { ARRAY_LAZY_LOWERBOUND = 1024 }; + +/* default initial size of a run container + setting it to zero delays the malloc.*/ +enum { RUN_DEFAULT_INIT_SIZE = 0 }; + +/* default initial size of an array container + setting it to zero delays the malloc */ +enum { ARRAY_DEFAULT_INIT_SIZE = 0 }; + +/* automatic bitset conversion during lazy or */ +#ifndef LAZY_OR_BITSET_CONVERSION +#define LAZY_OR_BITSET_CONVERSION true +#endif + +/* automatically attempt to convert a bitset to a full run during lazy + * evaluation */ +#ifndef LAZY_OR_BITSET_CONVERSION_TO_FULL +#define LAZY_OR_BITSET_CONVERSION_TO_FULL true +#endif + +/* automatically attempt to convert a bitset to a full run */ +#ifndef OR_BITSET_CONVERSION_TO_FULL +#define OR_BITSET_CONVERSION_TO_FULL true +#endif + +#endif +/* end file include/roaring/containers/perfparameters.h */ +/* begin file include/roaring/array_util.h */ +#ifndef ARRAY_UTIL_H +#define ARRAY_UTIL_H + +#include // for size_t +#include + + +/* + * Good old binary search. + * Assumes that array is sorted, has logarithmic complexity. + * if the result is x, then: + * if ( x>0 ) you have array[x] = ikey + * if ( x<0 ) then inserting ikey at position -x-1 in array (insuring that array[-x-1]=ikey) + * keys the array sorted. + */ +static inline int32_t binarySearch(const uint16_t *array, int32_t lenarray, + uint16_t ikey) { + int32_t low = 0; + int32_t high = lenarray - 1; + while (low <= high) { + int32_t middleIndex = (low + high) >> 1; + uint16_t middleValue = array[middleIndex]; + if (middleValue < ikey) { + low = middleIndex + 1; + } else if (middleValue > ikey) { + high = middleIndex - 1; + } else { + return middleIndex; + } + } + return -(low + 1); +} + +/** + * Galloping search + * Assumes that array is sorted, has logarithmic complexity. + * if the result is x, then if x = length, you have that all values in array between pos and length + * are smaller than min. + * otherwise returns the first index x such that array[x] >= min. + */ +static inline int32_t advanceUntil(const uint16_t *array, int32_t pos, + int32_t length, uint16_t min) { + int32_t lower = pos + 1; + + if ((lower >= length) || (array[lower] >= min)) { + return lower; + } + + int32_t spansize = 1; + + while ((lower + spansize < length) && (array[lower + spansize] < min)) { + spansize <<= 1; + } + int32_t upper = (lower + spansize < length) ? lower + spansize : length - 1; + + if (array[upper] == min) { + return upper; + } + if (array[upper] < min) { + // means + // array + // has no + // item + // >= min + // pos = array.length; + return length; + } + + // we know that the next-smallest span was too small + lower += (spansize >> 1); + + int32_t mid = 0; + while (lower + 1 != upper) { + mid = (lower + upper) >> 1; + if (array[mid] == min) { + return mid; + } else if (array[mid] < min) { + lower = mid; + } else { + upper = mid; + } + } + return upper; +} + +/** + * Returns number of elements which are less then $ikey. + * Array elements must be unique and sorted. + */ +static inline int32_t count_less(const uint16_t *array, int32_t lenarray, + uint16_t ikey) { + if (lenarray == 0) return 0; + int32_t pos = binarySearch(array, lenarray, ikey); + return pos >= 0 ? pos : -(pos+1); +} + +/** + * Returns number of elements which are greater then $ikey. + * Array elements must be unique and sorted. + */ +static inline int32_t count_greater(const uint16_t *array, int32_t lenarray, + uint16_t ikey) { + if (lenarray == 0) return 0; + int32_t pos = binarySearch(array, lenarray, ikey); + if (pos >= 0) { + return lenarray - (pos+1); + } else { + return lenarray - (-pos-1); + } +} + +/** + * From Schlegel et al., Fast Sorted-Set Intersection using SIMD Instructions + * Optimized by D. Lemire on May 3rd 2013 + * + * C should have capacity greater than the minimum of s_1 and s_b + 8 + * where 8 is sizeof(__m128i)/sizeof(uint16_t). + */ +int32_t intersect_vector16(const uint16_t *__restrict__ A, size_t s_a, + const uint16_t *__restrict__ B, size_t s_b, + uint16_t *C); + +/** + * Compute the cardinality of the intersection using SSE4 instructions + */ +int32_t intersect_vector16_cardinality(const uint16_t *__restrict__ A, + size_t s_a, + const uint16_t *__restrict__ B, + size_t s_b); + +/* Computes the intersection between one small and one large set of uint16_t. + * Stores the result into buffer and return the number of elements. */ +int32_t intersect_skewed_uint16(const uint16_t *smallarray, size_t size_s, + const uint16_t *largearray, size_t size_l, + uint16_t *buffer); + +/* Computes the size of the intersection between one small and one large set of + * uint16_t. */ +int32_t intersect_skewed_uint16_cardinality(const uint16_t *smallarray, + size_t size_s, + const uint16_t *largearray, + size_t size_l); + + +/* Check whether the size of the intersection between one small and one large set of uint16_t is non-zero. */ +bool intersect_skewed_uint16_nonempty(const uint16_t *smallarray, size_t size_s, + const uint16_t *largearray, size_t size_l); +/** + * Generic intersection function. + */ +int32_t intersect_uint16(const uint16_t *A, const size_t lenA, + const uint16_t *B, const size_t lenB, uint16_t *out); +/** + * Compute the size of the intersection (generic). + */ +int32_t intersect_uint16_cardinality(const uint16_t *A, const size_t lenA, + const uint16_t *B, const size_t lenB); + +/** + * Checking whether the size of the intersection is non-zero. + */ +bool intersect_uint16_nonempty(const uint16_t *A, const size_t lenA, + const uint16_t *B, const size_t lenB); +/** + * Generic union function. + */ +size_t union_uint16(const uint16_t *set_1, size_t size_1, const uint16_t *set_2, + size_t size_2, uint16_t *buffer); + +/** + * Generic XOR function. + */ +int32_t xor_uint16(const uint16_t *array_1, int32_t card_1, + const uint16_t *array_2, int32_t card_2, uint16_t *out); + +/** + * Generic difference function (ANDNOT). + */ +int difference_uint16(const uint16_t *a1, int length1, const uint16_t *a2, + int length2, uint16_t *a_out); + +/** + * Generic intersection function. + */ +size_t intersection_uint32(const uint32_t *A, const size_t lenA, + const uint32_t *B, const size_t lenB, uint32_t *out); + +/** + * Generic intersection function, returns just the cardinality. + */ +size_t intersection_uint32_card(const uint32_t *A, const size_t lenA, + const uint32_t *B, const size_t lenB); + +/** + * Generic union function. + */ +size_t union_uint32(const uint32_t *set_1, size_t size_1, const uint32_t *set_2, + size_t size_2, uint32_t *buffer); + +/** + * A fast SSE-based union function. + */ +uint32_t union_vector16(const uint16_t *__restrict__ set_1, uint32_t size_1, + const uint16_t *__restrict__ set_2, uint32_t size_2, + uint16_t *__restrict__ buffer); +/** + * A fast SSE-based XOR function. + */ +uint32_t xor_vector16(const uint16_t *__restrict__ array1, uint32_t length1, + const uint16_t *__restrict__ array2, uint32_t length2, + uint16_t *__restrict__ output); + +/** + * A fast SSE-based difference function. + */ +int32_t difference_vector16(const uint16_t *__restrict__ A, size_t s_a, + const uint16_t *__restrict__ B, size_t s_b, + uint16_t *C); + +/** + * Generic union function, returns just the cardinality. + */ +size_t union_uint32_card(const uint32_t *set_1, size_t size_1, + const uint32_t *set_2, size_t size_2); + +/** +* combines union_uint16 and union_vector16 optimally +*/ +size_t fast_union_uint16(const uint16_t *set_1, size_t size_1, const uint16_t *set_2, + size_t size_2, uint16_t *buffer); + + +bool memequals(const void *s1, const void *s2, size_t n); + +#endif +/* end file include/roaring/array_util.h */ +/* begin file include/roaring/roaring_types.h */ +/* + Typedefs used by various components +*/ + +#ifndef ROARING_TYPES_H +#define ROARING_TYPES_H + +typedef bool (*roaring_iterator)(uint32_t value, void *param); +typedef bool (*roaring_iterator64)(uint64_t value, void *param); + +/** +* (For advanced users.) +* The roaring_statistics_t can be used to collect detailed statistics about +* the composition of a roaring bitmap. +*/ +typedef struct roaring_statistics_s { + uint32_t n_containers; /* number of containers */ + + uint32_t n_array_containers; /* number of array containers */ + uint32_t n_run_containers; /* number of run containers */ + uint32_t n_bitset_containers; /* number of bitmap containers */ + + uint32_t + n_values_array_containers; /* number of values in array containers */ + uint32_t n_values_run_containers; /* number of values in run containers */ + uint32_t + n_values_bitset_containers; /* number of values in bitmap containers */ + + uint32_t n_bytes_array_containers; /* number of allocated bytes in array + containers */ + uint32_t n_bytes_run_containers; /* number of allocated bytes in run + containers */ + uint32_t n_bytes_bitset_containers; /* number of allocated bytes in bitmap + containers */ + + uint32_t + max_value; /* the maximal value, undefined if cardinality is zero */ + uint32_t + min_value; /* the minimal value, undefined if cardinality is zero */ + uint64_t sum_value; /* the sum of all values (could be used to compute + average) */ + + uint64_t cardinality; /* total number of values stored in the bitmap */ + + // and n_values_arrays, n_values_rle, n_values_bitmap +} roaring_statistics_t; + +#endif /* ROARING_TYPES_H */ +/* end file include/roaring/roaring_types.h */ +/* begin file include/roaring/utilasm.h */ +/* + * utilasm.h + * + */ + +#ifndef INCLUDE_UTILASM_H_ +#define INCLUDE_UTILASM_H_ + + +#if defined(USE_BMI) & defined(ROARING_INLINE_ASM) +#define ASMBITMANIPOPTIMIZATION // optimization flag + +#define ASM_SHIFT_RIGHT(srcReg, bitsReg, destReg) \ + __asm volatile("shrx %1, %2, %0" \ + : "=r"(destReg) \ + : /* write */ \ + "r"(bitsReg), /* read only */ \ + "r"(srcReg) /* read only */ \ + ) + +#define ASM_INPLACESHIFT_RIGHT(srcReg, bitsReg) \ + __asm volatile("shrx %1, %0, %0" \ + : "+r"(srcReg) \ + : /* read/write */ \ + "r"(bitsReg) /* read only */ \ + ) + +#define ASM_SHIFT_LEFT(srcReg, bitsReg, destReg) \ + __asm volatile("shlx %1, %2, %0" \ + : "=r"(destReg) \ + : /* write */ \ + "r"(bitsReg), /* read only */ \ + "r"(srcReg) /* read only */ \ + ) +// set bit at position testBit within testByte to 1 and +// copy cmovDst to cmovSrc if that bit was previously clear +#define ASM_SET_BIT_INC_WAS_CLEAR(testByte, testBit, count) \ + __asm volatile( \ + "bts %2, %0\n" \ + "sbb $-1, %1\n" \ + : "+r"(testByte), /* read/write */ \ + "+r"(count) \ + : /* read/write */ \ + "r"(testBit) /* read only */ \ + ) + +#define ASM_CLEAR_BIT_DEC_WAS_SET(testByte, testBit, count) \ + __asm volatile( \ + "btr %2, %0\n" \ + "sbb $0, %1\n" \ + : "+r"(testByte), /* read/write */ \ + "+r"(count) \ + : /* read/write */ \ + "r"(testBit) /* read only */ \ + ) + +#define ASM_BT64(testByte, testBit, count) \ + __asm volatile( \ + "bt %2,%1\n" \ + "sbb %0,%0" /*could use setb */ \ + : "=r"(count) \ + : /* write */ \ + "r"(testByte), /* read only */ \ + "r"(testBit) /* read only */ \ + ) + +#endif // USE_BMI +#endif /* INCLUDE_UTILASM_H_ */ +/* end file include/roaring/utilasm.h */ +/* begin file include/roaring/bitset_util.h */ +#ifndef BITSET_UTIL_H +#define BITSET_UTIL_H + +#include + + +/* + * Set all bits in indexes [begin,end) to true. + */ +static inline void bitset_set_range(uint64_t *bitmap, uint32_t start, + uint32_t end) { + if (start == end) return; + uint32_t firstword = start / 64; + uint32_t endword = (end - 1) / 64; + if (firstword == endword) { + bitmap[firstword] |= ((~UINT64_C(0)) << (start % 64)) & + ((~UINT64_C(0)) >> ((~end + 1) % 64)); + return; + } + bitmap[firstword] |= (~UINT64_C(0)) << (start % 64); + for (uint32_t i = firstword + 1; i < endword; i++) bitmap[i] = ~UINT64_C(0); + bitmap[endword] |= (~UINT64_C(0)) >> ((~end + 1) % 64); +} + + +/* + * Find the cardinality of the bitset in [begin,begin+lenminusone] + */ +static inline int bitset_lenrange_cardinality(uint64_t *bitmap, uint32_t start, + uint32_t lenminusone) { + uint32_t firstword = start / 64; + uint32_t endword = (start + lenminusone) / 64; + if (firstword == endword) { + return hamming(bitmap[firstword] & + ((~UINT64_C(0)) >> ((63 - lenminusone) % 64)) + << (start % 64)); + } + int answer = hamming(bitmap[firstword] & ((~UINT64_C(0)) << (start % 64))); + for (uint32_t i = firstword + 1; i < endword; i++) { + answer += hamming(bitmap[i]); + } + answer += + hamming(bitmap[endword] & + (~UINT64_C(0)) >> (((~start + 1) - lenminusone - 1) % 64)); + return answer; +} + +/* + * Check whether the cardinality of the bitset in [begin,begin+lenminusone] is 0 + */ +static inline bool bitset_lenrange_empty(uint64_t *bitmap, uint32_t start, + uint32_t lenminusone) { + uint32_t firstword = start / 64; + uint32_t endword = (start + lenminusone) / 64; + if (firstword == endword) { + return (bitmap[firstword] & ((~UINT64_C(0)) >> ((63 - lenminusone) % 64)) + << (start % 64)) == 0; + } + if(((bitmap[firstword] & ((~UINT64_C(0)) << (start%64)))) != 0) return false; + for (uint32_t i = firstword + 1; i < endword; i++) { + if(bitmap[i] != 0) return false; + } + if((bitmap[endword] & (~UINT64_C(0)) >> (((~start + 1) - lenminusone - 1) % 64)) != 0) return false; + return true; +} + + +/* + * Set all bits in indexes [begin,begin+lenminusone] to true. + */ +static inline void bitset_set_lenrange(uint64_t *bitmap, uint32_t start, + uint32_t lenminusone) { + uint32_t firstword = start / 64; + uint32_t endword = (start + lenminusone) / 64; + if (firstword == endword) { + bitmap[firstword] |= ((~UINT64_C(0)) >> ((63 - lenminusone) % 64)) + << (start % 64); + return; + } + uint64_t temp = bitmap[endword]; + bitmap[firstword] |= (~UINT64_C(0)) << (start % 64); + for (uint32_t i = firstword + 1; i < endword; i += 2) + bitmap[i] = bitmap[i + 1] = ~UINT64_C(0); + bitmap[endword] = + temp | (~UINT64_C(0)) >> (((~start + 1) - lenminusone - 1) % 64); +} + +/* + * Flip all the bits in indexes [begin,end). + */ +static inline void bitset_flip_range(uint64_t *bitmap, uint32_t start, + uint32_t end) { + if (start == end) return; + uint32_t firstword = start / 64; + uint32_t endword = (end - 1) / 64; + bitmap[firstword] ^= ~((~UINT64_C(0)) << (start % 64)); + for (uint32_t i = firstword; i < endword; i++) bitmap[i] = ~bitmap[i]; + bitmap[endword] ^= ((~UINT64_C(0)) >> ((~end + 1) % 64)); +} + +/* + * Set all bits in indexes [begin,end) to false. + */ +static inline void bitset_reset_range(uint64_t *bitmap, uint32_t start, + uint32_t end) { + if (start == end) return; + uint32_t firstword = start / 64; + uint32_t endword = (end - 1) / 64; + if (firstword == endword) { + bitmap[firstword] &= ~(((~UINT64_C(0)) << (start % 64)) & + ((~UINT64_C(0)) >> ((~end + 1) % 64))); + return; + } + bitmap[firstword] &= ~((~UINT64_C(0)) << (start % 64)); + for (uint32_t i = firstword + 1; i < endword; i++) bitmap[i] = UINT64_C(0); + bitmap[endword] &= ~((~UINT64_C(0)) >> ((~end + 1) % 64)); +} + +/* + * Given a bitset containing "length" 64-bit words, write out the position + * of all the set bits to "out", values start at "base". + * + * The "out" pointer should be sufficient to store the actual number of bits + * set. + * + * Returns how many values were actually decoded. + * + * This function should only be expected to be faster than + * bitset_extract_setbits + * when the density of the bitset is high. + * + * This function uses AVX2 decoding. + */ +size_t bitset_extract_setbits_avx2(uint64_t *bitset, size_t length, void *vout, + size_t outcapacity, uint32_t base); + +/* + * Given a bitset containing "length" 64-bit words, write out the position + * of all the set bits to "out", values start at "base". + * + * The "out" pointer should be sufficient to store the actual number of bits + *set. + * + * Returns how many values were actually decoded. + */ +size_t bitset_extract_setbits(uint64_t *bitset, size_t length, void *vout, + uint32_t base); + +/* + * Given a bitset containing "length" 64-bit words, write out the position + * of all the set bits to "out" as 16-bit integers, values start at "base" (can + *be set to zero) + * + * The "out" pointer should be sufficient to store the actual number of bits + *set. + * + * Returns how many values were actually decoded. + * + * This function should only be expected to be faster than + *bitset_extract_setbits_uint16 + * when the density of the bitset is high. + * + * This function uses SSE decoding. + */ +size_t bitset_extract_setbits_sse_uint16(const uint64_t *bitset, size_t length, + uint16_t *out, size_t outcapacity, + uint16_t base); + +/* + * Given a bitset containing "length" 64-bit words, write out the position + * of all the set bits to "out", values start at "base" + * (can be set to zero) + * + * The "out" pointer should be sufficient to store the actual number of bits + *set. + * + * Returns how many values were actually decoded. + */ +size_t bitset_extract_setbits_uint16(const uint64_t *bitset, size_t length, + uint16_t *out, uint16_t base); + +/* + * Given two bitsets containing "length" 64-bit words, write out the position + * of all the common set bits to "out", values start at "base" + * (can be set to zero) + * + * The "out" pointer should be sufficient to store the actual number of bits + * set. + * + * Returns how many values were actually decoded. + */ +size_t bitset_extract_intersection_setbits_uint16(const uint64_t * __restrict__ bitset1, + const uint64_t * __restrict__ bitset2, + size_t length, uint16_t *out, + uint16_t base); + +/* + * Given a bitset having cardinality card, set all bit values in the list (there + * are length of them) + * and return the updated cardinality. This evidently assumes that the bitset + * already contained data. + */ +uint64_t bitset_set_list_withcard(void *bitset, uint64_t card, + const uint16_t *list, uint64_t length); +/* + * Given a bitset, set all bit values in the list (there + * are length of them). + */ +void bitset_set_list(void *bitset, const uint16_t *list, uint64_t length); + +/* + * Given a bitset having cardinality card, unset all bit values in the list + * (there are length of them) + * and return the updated cardinality. This evidently assumes that the bitset + * already contained data. + */ +uint64_t bitset_clear_list(void *bitset, uint64_t card, const uint16_t *list, + uint64_t length); + +/* + * Given a bitset having cardinality card, toggle all bit values in the list + * (there are length of them) + * and return the updated cardinality. This evidently assumes that the bitset + * already contained data. + */ + +uint64_t bitset_flip_list_withcard(void *bitset, uint64_t card, + const uint16_t *list, uint64_t length); + +void bitset_flip_list(void *bitset, const uint16_t *list, uint64_t length); + +#ifdef USEAVX +/*** + * BEGIN Harley-Seal popcount functions. + */ + +/** + * Compute the population count of a 256-bit word + * This is not especially fast, but it is convenient as part of other functions. + */ +static inline __m256i popcount256(__m256i v) { + const __m256i lookuppos = _mm256_setr_epi8( + /* 0 */ 4 + 0, /* 1 */ 4 + 1, /* 2 */ 4 + 1, /* 3 */ 4 + 2, + /* 4 */ 4 + 1, /* 5 */ 4 + 2, /* 6 */ 4 + 2, /* 7 */ 4 + 3, + /* 8 */ 4 + 1, /* 9 */ 4 + 2, /* a */ 4 + 2, /* b */ 4 + 3, + /* c */ 4 + 2, /* d */ 4 + 3, /* e */ 4 + 3, /* f */ 4 + 4, + + /* 0 */ 4 + 0, /* 1 */ 4 + 1, /* 2 */ 4 + 1, /* 3 */ 4 + 2, + /* 4 */ 4 + 1, /* 5 */ 4 + 2, /* 6 */ 4 + 2, /* 7 */ 4 + 3, + /* 8 */ 4 + 1, /* 9 */ 4 + 2, /* a */ 4 + 2, /* b */ 4 + 3, + /* c */ 4 + 2, /* d */ 4 + 3, /* e */ 4 + 3, /* f */ 4 + 4); + const __m256i lookupneg = _mm256_setr_epi8( + /* 0 */ 4 - 0, /* 1 */ 4 - 1, /* 2 */ 4 - 1, /* 3 */ 4 - 2, + /* 4 */ 4 - 1, /* 5 */ 4 - 2, /* 6 */ 4 - 2, /* 7 */ 4 - 3, + /* 8 */ 4 - 1, /* 9 */ 4 - 2, /* a */ 4 - 2, /* b */ 4 - 3, + /* c */ 4 - 2, /* d */ 4 - 3, /* e */ 4 - 3, /* f */ 4 - 4, + + /* 0 */ 4 - 0, /* 1 */ 4 - 1, /* 2 */ 4 - 1, /* 3 */ 4 - 2, + /* 4 */ 4 - 1, /* 5 */ 4 - 2, /* 6 */ 4 - 2, /* 7 */ 4 - 3, + /* 8 */ 4 - 1, /* 9 */ 4 - 2, /* a */ 4 - 2, /* b */ 4 - 3, + /* c */ 4 - 2, /* d */ 4 - 3, /* e */ 4 - 3, /* f */ 4 - 4); + const __m256i low_mask = _mm256_set1_epi8(0x0f); + + const __m256i lo = _mm256_and_si256(v, low_mask); + const __m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), low_mask); + const __m256i popcnt1 = _mm256_shuffle_epi8(lookuppos, lo); + const __m256i popcnt2 = _mm256_shuffle_epi8(lookupneg, hi); + return _mm256_sad_epu8(popcnt1, popcnt2); +} + +/** + * Simple CSA over 256 bits + */ +static inline void CSA(__m256i *h, __m256i *l, __m256i a, __m256i b, + __m256i c) { + const __m256i u = _mm256_xor_si256(a, b); + *h = _mm256_or_si256(_mm256_and_si256(a, b), _mm256_and_si256(u, c)); + *l = _mm256_xor_si256(u, c); +} + +/** + * Fast Harley-Seal AVX population count function + */ +inline static uint64_t avx2_harley_seal_popcount256(const __m256i *data, + const uint64_t size) { + __m256i total = _mm256_setzero_si256(); + __m256i ones = _mm256_setzero_si256(); + __m256i twos = _mm256_setzero_si256(); + __m256i fours = _mm256_setzero_si256(); + __m256i eights = _mm256_setzero_si256(); + __m256i sixteens = _mm256_setzero_si256(); + __m256i twosA, twosB, foursA, foursB, eightsA, eightsB; + + const uint64_t limit = size - size % 16; + uint64_t i = 0; + + for (; i < limit; i += 16) { + CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i), + _mm256_lddqu_si256(data + i + 1)); + CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 2), + _mm256_lddqu_si256(data + i + 3)); + CSA(&foursA, &twos, twos, twosA, twosB); + CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i + 4), + _mm256_lddqu_si256(data + i + 5)); + CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 6), + _mm256_lddqu_si256(data + i + 7)); + CSA(&foursB, &twos, twos, twosA, twosB); + CSA(&eightsA, &fours, fours, foursA, foursB); + CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i + 8), + _mm256_lddqu_si256(data + i + 9)); + CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 10), + _mm256_lddqu_si256(data + i + 11)); + CSA(&foursA, &twos, twos, twosA, twosB); + CSA(&twosA, &ones, ones, _mm256_lddqu_si256(data + i + 12), + _mm256_lddqu_si256(data + i + 13)); + CSA(&twosB, &ones, ones, _mm256_lddqu_si256(data + i + 14), + _mm256_lddqu_si256(data + i + 15)); + CSA(&foursB, &twos, twos, twosA, twosB); + CSA(&eightsB, &fours, fours, foursA, foursB); + CSA(&sixteens, &eights, eights, eightsA, eightsB); + + total = _mm256_add_epi64(total, popcount256(sixteens)); + } + + total = _mm256_slli_epi64(total, 4); // * 16 + total = _mm256_add_epi64( + total, _mm256_slli_epi64(popcount256(eights), 3)); // += 8 * ... + total = _mm256_add_epi64( + total, _mm256_slli_epi64(popcount256(fours), 2)); // += 4 * ... + total = _mm256_add_epi64( + total, _mm256_slli_epi64(popcount256(twos), 1)); // += 2 * ... + total = _mm256_add_epi64(total, popcount256(ones)); + for (; i < size; i++) + total = + _mm256_add_epi64(total, popcount256(_mm256_lddqu_si256(data + i))); + + return (uint64_t)(_mm256_extract_epi64(total, 0)) + + (uint64_t)(_mm256_extract_epi64(total, 1)) + + (uint64_t)(_mm256_extract_epi64(total, 2)) + + (uint64_t)(_mm256_extract_epi64(total, 3)); +} + +#define AVXPOPCNTFNC(opname, avx_intrinsic) \ + static inline uint64_t avx2_harley_seal_popcount256_##opname( \ + const __m256i *data1, const __m256i *data2, const uint64_t size) { \ + __m256i total = _mm256_setzero_si256(); \ + __m256i ones = _mm256_setzero_si256(); \ + __m256i twos = _mm256_setzero_si256(); \ + __m256i fours = _mm256_setzero_si256(); \ + __m256i eights = _mm256_setzero_si256(); \ + __m256i sixteens = _mm256_setzero_si256(); \ + __m256i twosA, twosB, foursA, foursB, eightsA, eightsB; \ + __m256i A1, A2; \ + const uint64_t limit = size - size % 16; \ + uint64_t i = 0; \ + for (; i < limit; i += 16) { \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \ + _mm256_lddqu_si256(data2 + i)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 1), \ + _mm256_lddqu_si256(data2 + i + 1)); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 2), \ + _mm256_lddqu_si256(data2 + i + 2)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 3), \ + _mm256_lddqu_si256(data2 + i + 3)); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursA, &twos, twos, twosA, twosB); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 4), \ + _mm256_lddqu_si256(data2 + i + 4)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 5), \ + _mm256_lddqu_si256(data2 + i + 5)); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 6), \ + _mm256_lddqu_si256(data2 + i + 6)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 7), \ + _mm256_lddqu_si256(data2 + i + 7)); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursB, &twos, twos, twosA, twosB); \ + CSA(&eightsA, &fours, fours, foursA, foursB); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 8), \ + _mm256_lddqu_si256(data2 + i + 8)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 9), \ + _mm256_lddqu_si256(data2 + i + 9)); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 10), \ + _mm256_lddqu_si256(data2 + i + 10)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 11), \ + _mm256_lddqu_si256(data2 + i + 11)); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursA, &twos, twos, twosA, twosB); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 12), \ + _mm256_lddqu_si256(data2 + i + 12)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 13), \ + _mm256_lddqu_si256(data2 + i + 13)); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 14), \ + _mm256_lddqu_si256(data2 + i + 14)); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 15), \ + _mm256_lddqu_si256(data2 + i + 15)); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursB, &twos, twos, twosA, twosB); \ + CSA(&eightsB, &fours, fours, foursA, foursB); \ + CSA(&sixteens, &eights, eights, eightsA, eightsB); \ + total = _mm256_add_epi64(total, popcount256(sixteens)); \ + } \ + total = _mm256_slli_epi64(total, 4); \ + total = _mm256_add_epi64(total, \ + _mm256_slli_epi64(popcount256(eights), 3)); \ + total = \ + _mm256_add_epi64(total, _mm256_slli_epi64(popcount256(fours), 2)); \ + total = \ + _mm256_add_epi64(total, _mm256_slli_epi64(popcount256(twos), 1)); \ + total = _mm256_add_epi64(total, popcount256(ones)); \ + for (; i < size; i++) { \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \ + _mm256_lddqu_si256(data2 + i)); \ + total = _mm256_add_epi64(total, popcount256(A1)); \ + } \ + return (uint64_t)(_mm256_extract_epi64(total, 0)) + \ + (uint64_t)(_mm256_extract_epi64(total, 1)) + \ + (uint64_t)(_mm256_extract_epi64(total, 2)) + \ + (uint64_t)(_mm256_extract_epi64(total, 3)); \ + } \ + static inline uint64_t avx2_harley_seal_popcount256andstore_##opname( \ + const __m256i *__restrict__ data1, const __m256i *__restrict__ data2, \ + __m256i *__restrict__ out, const uint64_t size) { \ + __m256i total = _mm256_setzero_si256(); \ + __m256i ones = _mm256_setzero_si256(); \ + __m256i twos = _mm256_setzero_si256(); \ + __m256i fours = _mm256_setzero_si256(); \ + __m256i eights = _mm256_setzero_si256(); \ + __m256i sixteens = _mm256_setzero_si256(); \ + __m256i twosA, twosB, foursA, foursB, eightsA, eightsB; \ + __m256i A1, A2; \ + const uint64_t limit = size - size % 16; \ + uint64_t i = 0; \ + for (; i < limit; i += 16) { \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \ + _mm256_lddqu_si256(data2 + i)); \ + _mm256_storeu_si256(out + i, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 1), \ + _mm256_lddqu_si256(data2 + i + 1)); \ + _mm256_storeu_si256(out + i + 1, A2); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 2), \ + _mm256_lddqu_si256(data2 + i + 2)); \ + _mm256_storeu_si256(out + i + 2, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 3), \ + _mm256_lddqu_si256(data2 + i + 3)); \ + _mm256_storeu_si256(out + i + 3, A2); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursA, &twos, twos, twosA, twosB); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 4), \ + _mm256_lddqu_si256(data2 + i + 4)); \ + _mm256_storeu_si256(out + i + 4, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 5), \ + _mm256_lddqu_si256(data2 + i + 5)); \ + _mm256_storeu_si256(out + i + 5, A2); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 6), \ + _mm256_lddqu_si256(data2 + i + 6)); \ + _mm256_storeu_si256(out + i + 6, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 7), \ + _mm256_lddqu_si256(data2 + i + 7)); \ + _mm256_storeu_si256(out + i + 7, A2); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursB, &twos, twos, twosA, twosB); \ + CSA(&eightsA, &fours, fours, foursA, foursB); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 8), \ + _mm256_lddqu_si256(data2 + i + 8)); \ + _mm256_storeu_si256(out + i + 8, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 9), \ + _mm256_lddqu_si256(data2 + i + 9)); \ + _mm256_storeu_si256(out + i + 9, A2); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 10), \ + _mm256_lddqu_si256(data2 + i + 10)); \ + _mm256_storeu_si256(out + i + 10, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 11), \ + _mm256_lddqu_si256(data2 + i + 11)); \ + _mm256_storeu_si256(out + i + 11, A2); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursA, &twos, twos, twosA, twosB); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 12), \ + _mm256_lddqu_si256(data2 + i + 12)); \ + _mm256_storeu_si256(out + i + 12, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 13), \ + _mm256_lddqu_si256(data2 + i + 13)); \ + _mm256_storeu_si256(out + i + 13, A2); \ + CSA(&twosA, &ones, ones, A1, A2); \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 14), \ + _mm256_lddqu_si256(data2 + i + 14)); \ + _mm256_storeu_si256(out + i + 14, A1); \ + A2 = avx_intrinsic(_mm256_lddqu_si256(data1 + i + 15), \ + _mm256_lddqu_si256(data2 + i + 15)); \ + _mm256_storeu_si256(out + i + 15, A2); \ + CSA(&twosB, &ones, ones, A1, A2); \ + CSA(&foursB, &twos, twos, twosA, twosB); \ + CSA(&eightsB, &fours, fours, foursA, foursB); \ + CSA(&sixteens, &eights, eights, eightsA, eightsB); \ + total = _mm256_add_epi64(total, popcount256(sixteens)); \ + } \ + total = _mm256_slli_epi64(total, 4); \ + total = _mm256_add_epi64(total, \ + _mm256_slli_epi64(popcount256(eights), 3)); \ + total = \ + _mm256_add_epi64(total, _mm256_slli_epi64(popcount256(fours), 2)); \ + total = \ + _mm256_add_epi64(total, _mm256_slli_epi64(popcount256(twos), 1)); \ + total = _mm256_add_epi64(total, popcount256(ones)); \ + for (; i < size; i++) { \ + A1 = avx_intrinsic(_mm256_lddqu_si256(data1 + i), \ + _mm256_lddqu_si256(data2 + i)); \ + _mm256_storeu_si256(out + i, A1); \ + total = _mm256_add_epi64(total, popcount256(A1)); \ + } \ + return (uint64_t)(_mm256_extract_epi64(total, 0)) + \ + (uint64_t)(_mm256_extract_epi64(total, 1)) + \ + (uint64_t)(_mm256_extract_epi64(total, 2)) + \ + (uint64_t)(_mm256_extract_epi64(total, 3)); \ + } + +AVXPOPCNTFNC(or, _mm256_or_si256) +AVXPOPCNTFNC(union, _mm256_or_si256) +AVXPOPCNTFNC(and, _mm256_and_si256) +AVXPOPCNTFNC(intersection, _mm256_and_si256) +AVXPOPCNTFNC (xor, _mm256_xor_si256) +AVXPOPCNTFNC(andnot, _mm256_andnot_si256) + +/*** + * END Harley-Seal popcount functions. + */ + +#endif // USEAVX + +#endif +/* end file include/roaring/bitset_util.h */ +/* begin file include/roaring/containers/array.h */ +/* + * array.h + * + */ + +#ifndef INCLUDE_CONTAINERS_ARRAY_H_ +#define INCLUDE_CONTAINERS_ARRAY_H_ + +#include + + +/* Containers with DEFAULT_MAX_SIZE or less integers should be arrays */ +enum { DEFAULT_MAX_SIZE = 4096 }; + +/* struct array_container - sparse representation of a bitmap + * + * @cardinality: number of indices in `array` (and the bitmap) + * @capacity: allocated size of `array` + * @array: sorted list of integers + */ +struct array_container_s { + int32_t cardinality; + int32_t capacity; + uint16_t *array; +}; + +typedef struct array_container_s array_container_t; + +/* Create a new array with default. Return NULL in case of failure. See also + * array_container_create_given_capacity. */ +array_container_t *array_container_create(void); + +/* Create a new array with a specified capacity size. Return NULL in case of + * failure. */ +array_container_t *array_container_create_given_capacity(int32_t size); + +/* Create a new array containing all values in [min,max). */ +array_container_t * array_container_create_range(uint32_t min, uint32_t max); + +/* + * Shrink the capacity to the actual size, return the number of bytes saved. + */ +int array_container_shrink_to_fit(array_container_t *src); + +/* Free memory owned by `array'. */ +void array_container_free(array_container_t *array); + +/* Duplicate container */ +array_container_t *array_container_clone(const array_container_t *src); + +int32_t array_container_serialize(const array_container_t *container, + char *buf) WARN_UNUSED; + +uint32_t array_container_serialization_len(const array_container_t *container); + +void *array_container_deserialize(const char *buf, size_t buf_len); + +/* Get the cardinality of `array'. */ +static inline int array_container_cardinality(const array_container_t *array) { + return array->cardinality; +} + +static inline bool array_container_nonzero_cardinality( + const array_container_t *array) { + return array->cardinality > 0; +} + +/* Copy one container into another. We assume that they are distinct. */ +void array_container_copy(const array_container_t *src, array_container_t *dst); + +/* Add all the values in [min,max) (included) at a distance k*step from min. + The container must have a size less or equal to DEFAULT_MAX_SIZE after this + addition. */ +void array_container_add_from_range(array_container_t *arr, uint32_t min, + uint32_t max, uint16_t step); + +/* Set the cardinality to zero (does not release memory). */ +static inline void array_container_clear(array_container_t *array) { + array->cardinality = 0; +} + +static inline bool array_container_empty(const array_container_t *array) { + return array->cardinality == 0; +} + +/* check whether the cardinality is equal to the capacity (this does not mean +* that it contains 1<<16 elements) */ +static inline bool array_container_full(const array_container_t *array) { + return array->cardinality == array->capacity; +} + + +/* Compute the union of `src_1' and `src_2' and write the result to `dst' + * It is assumed that `dst' is distinct from both `src_1' and `src_2'. */ +void array_container_union(const array_container_t *src_1, + const array_container_t *src_2, + array_container_t *dst); + +/* symmetric difference, see array_container_union */ +void array_container_xor(const array_container_t *array_1, + const array_container_t *array_2, + array_container_t *out); + +/* Computes the intersection of src_1 and src_2 and write the result to + * dst. It is assumed that dst is distinct from both src_1 and src_2. */ +void array_container_intersection(const array_container_t *src_1, + const array_container_t *src_2, + array_container_t *dst); + +/* Check whether src_1 and src_2 intersect. */ +bool array_container_intersect(const array_container_t *src_1, + const array_container_t *src_2); + + +/* computers the size of the intersection between two arrays. + */ +int array_container_intersection_cardinality(const array_container_t *src_1, + const array_container_t *src_2); + +/* computes the intersection of array1 and array2 and write the result to + * array1. + * */ +void array_container_intersection_inplace(array_container_t *src_1, + const array_container_t *src_2); + +/* + * Write out the 16-bit integers contained in this container as a list of 32-bit + * integers using base + * as the starting value (it might be expected that base has zeros in its 16 + * least significant bits). + * The function returns the number of values written. + * The caller is responsible for allocating enough memory in out. + */ +int array_container_to_uint32_array(void *vout, const array_container_t *cont, + uint32_t base); + +/* Compute the number of runs */ +int32_t array_container_number_of_runs(const array_container_t *a); + +/* + * Print this container using printf (useful for debugging). + */ +void array_container_printf(const array_container_t *v); + +/* + * Print this container using printf as a comma-separated list of 32-bit + * integers starting at base. + */ +void array_container_printf_as_uint32_array(const array_container_t *v, + uint32_t base); + +/** + * Return the serialized size in bytes of a container having cardinality "card". + */ +static inline int32_t array_container_serialized_size_in_bytes(int32_t card) { + return card * 2 + 2; +} + +/** + * Increase capacity to at least min. + * Whether the existing data needs to be copied over depends on the "preserve" + * parameter. If preserve is false, then the new content will be uninitialized, + * otherwise the old content is copied. + */ +void array_container_grow(array_container_t *container, int32_t min, + bool preserve); + +bool array_container_iterate(const array_container_t *cont, uint32_t base, + roaring_iterator iterator, void *ptr); +bool array_container_iterate64(const array_container_t *cont, uint32_t base, + roaring_iterator64 iterator, uint64_t high_bits, + void *ptr); + +/** + * Writes the underlying array to buf, outputs how many bytes were written. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes written should be + * array_container_size_in_bytes(container). + * + */ +int32_t array_container_write(const array_container_t *container, char *buf); +/** + * Reads the instance from buf, outputs how many bytes were read. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes read should be array_container_size_in_bytes(container). + * You need to provide the (known) cardinality. + */ +int32_t array_container_read(int32_t cardinality, array_container_t *container, + const char *buf); + +/** + * Return the serialized size in bytes of a container (see + * bitset_container_write) + * This is meant to be compatible with the Java and Go versions of Roaring and + * assumes + * that the cardinality of the container is already known. + * + */ +static inline int32_t array_container_size_in_bytes( + const array_container_t *container) { + return container->cardinality * sizeof(uint16_t); +} + +/** + * Return true if the two arrays have the same content. + */ +static inline bool array_container_equals( + const array_container_t *container1, + const array_container_t *container2) { + + if (container1->cardinality != container2->cardinality) { + return false; + } + return memequals(container1->array, container2->array, container1->cardinality*2); +} + +/** + * Return true if container1 is a subset of container2. + */ +bool array_container_is_subset(const array_container_t *container1, + const array_container_t *container2); + +/** + * If the element of given rank is in this container, supposing that the first + * element has rank start_rank, then the function returns true and sets element + * accordingly. + * Otherwise, it returns false and update start_rank. + */ +static inline bool array_container_select(const array_container_t *container, + uint32_t *start_rank, uint32_t rank, + uint32_t *element) { + int card = array_container_cardinality(container); + if (*start_rank + card <= rank) { + *start_rank += card; + return false; + } else { + *element = container->array[rank - *start_rank]; + return true; + } +} + +/* Computes the difference of array1 and array2 and write the result + * to array out. + * Array out does not need to be distinct from array_1 + */ +void array_container_andnot(const array_container_t *array_1, + const array_container_t *array_2, + array_container_t *out); + +/* Append x to the set. Assumes that the value is larger than any preceding + * values. */ +static inline void array_container_append(array_container_t *arr, + uint16_t pos) { + const int32_t capacity = arr->capacity; + + if (array_container_full(arr)) { + array_container_grow(arr, capacity + 1, true); + } + + arr->array[arr->cardinality++] = pos; +} + +/** + * Add value to the set if final cardinality doesn't exceed max_cardinality. + * Return code: + * 1 -- value was added + * 0 -- value was already present + * -1 -- value was not added because cardinality would exceed max_cardinality + */ +static inline int array_container_try_add(array_container_t *arr, uint16_t value, + int32_t max_cardinality) { + const int32_t cardinality = arr->cardinality; + + // best case, we can append. + if ((array_container_empty(arr) || arr->array[cardinality - 1] < value) && + cardinality < max_cardinality) { + array_container_append(arr, value); + return 1; + } + + const int32_t loc = binarySearch(arr->array, cardinality, value); + + if (loc >= 0) { + return 0; + } else if (cardinality < max_cardinality) { + if (array_container_full(arr)) { + array_container_grow(arr, arr->capacity + 1, true); + } + const int32_t insert_idx = -loc - 1; + memmove(arr->array + insert_idx + 1, arr->array + insert_idx, + (cardinality - insert_idx) * sizeof(uint16_t)); + arr->array[insert_idx] = value; + arr->cardinality++; + return 1; + } else { + return -1; + } +} + +/* Add value to the set. Returns true if x was not already present. */ +static inline bool array_container_add(array_container_t *arr, uint16_t value) { + return array_container_try_add(arr, value, INT32_MAX) == 1; +} + +/* Remove x from the set. Returns true if x was present. */ +static inline bool array_container_remove(array_container_t *arr, + uint16_t pos) { + const int32_t idx = binarySearch(arr->array, arr->cardinality, pos); + const bool is_present = idx >= 0; + if (is_present) { + memmove(arr->array + idx, arr->array + idx + 1, + (arr->cardinality - idx - 1) * sizeof(uint16_t)); + arr->cardinality--; + } + + return is_present; +} + +/* Check whether x is present. */ +static inline bool array_container_contains(const array_container_t *arr, + uint16_t pos) { + // return binarySearch(arr->array, arr->cardinality, pos) >= 0; + // binary search with fallback to linear search for short ranges + int32_t low = 0; + const uint16_t * carr = (const uint16_t *) arr->array; + int32_t high = arr->cardinality - 1; + // while (high - low >= 0) { + while(high >= low + 16) { + int32_t middleIndex = (low + high)>>1; + uint16_t middleValue = carr[middleIndex]; + if (middleValue < pos) { + low = middleIndex + 1; + } else if (middleValue > pos) { + high = middleIndex - 1; + } else { + return true; + } + } + + for (int i=low; i <= high; i++) { + uint16_t v = carr[i]; + if (v == pos) { + return true; + } + if ( v > pos ) return false; + } + return false; + +} + +//* Check whether a range of values from range_start (included) to range_end (excluded) is present. */ +static inline bool array_container_contains_range(const array_container_t *arr, + uint32_t range_start, uint32_t range_end) { + + const uint16_t rs_included = range_start; + const uint16_t re_included = range_end - 1; + + const uint16_t *carr = (const uint16_t *) arr->array; + + const int32_t start = advanceUntil(carr, -1, arr->cardinality, rs_included); + const int32_t end = advanceUntil(carr, start - 1, arr->cardinality, re_included); + + return (start < arr->cardinality) && (end < arr->cardinality) + && (((uint16_t)(end - start)) == re_included - rs_included) + && (carr[start] == rs_included) && (carr[end] == re_included); +} + +/* Returns the smallest value (assumes not empty) */ +static inline uint16_t array_container_minimum(const array_container_t *arr) { + if (arr->cardinality == 0) return 0; + return arr->array[0]; +} + +/* Returns the largest value (assumes not empty) */ +static inline uint16_t array_container_maximum(const array_container_t *arr) { + if (arr->cardinality == 0) return 0; + return arr->array[arr->cardinality - 1]; +} + +/* Returns the number of values equal or smaller than x */ +static inline int array_container_rank(const array_container_t *arr, uint16_t x) { + const int32_t idx = binarySearch(arr->array, arr->cardinality, x); + const bool is_present = idx >= 0; + if (is_present) { + return idx + 1; + } else { + return -idx - 1; + } +} + +/* Returns the index of the first value equal or smaller than x, or -1 */ +static inline int array_container_index_equalorlarger(const array_container_t *arr, uint16_t x) { + const int32_t idx = binarySearch(arr->array, arr->cardinality, x); + const bool is_present = idx >= 0; + if (is_present) { + return idx; + } else { + int32_t candidate = - idx - 1; + if(candidate < arr->cardinality) return candidate; + return -1; + } +} + +/* + * Adds all values in range [min,max] using hint: + * nvals_less is the number of array values less than $min + * nvals_greater is the number of array values greater than $max + */ +static inline void array_container_add_range_nvals(array_container_t *array, + uint32_t min, uint32_t max, + int32_t nvals_less, + int32_t nvals_greater) { + int32_t union_cardinality = nvals_less + (max - min + 1) + nvals_greater; + if (union_cardinality > array->capacity) { + array_container_grow(array, union_cardinality, true); + } + memmove(&(array->array[union_cardinality - nvals_greater]), + &(array->array[array->cardinality - nvals_greater]), + nvals_greater * sizeof(uint16_t)); + for (uint32_t i = 0; i <= max - min; i++) { + array->array[nvals_less + i] = min + i; + } + array->cardinality = union_cardinality; +} + +/** + * Adds all values in range [min,max]. + */ +static inline void array_container_add_range(array_container_t *array, + uint32_t min, uint32_t max) { + int32_t nvals_greater = count_greater(array->array, array->cardinality, max); + int32_t nvals_less = count_less(array->array, array->cardinality - nvals_greater, min); + array_container_add_range_nvals(array, min, max, nvals_less, nvals_greater); +} + +/* + * Removes all elements array[pos] .. array[pos+count-1] + */ +static inline void array_container_remove_range(array_container_t *array, + uint32_t pos, uint32_t count) { + if (count != 0) { + memmove(&(array->array[pos]), &(array->array[pos+count]), + (array->cardinality - pos - count) * sizeof(uint16_t)); + array->cardinality -= count; + } +} + +#endif /* INCLUDE_CONTAINERS_ARRAY_H_ */ +/* end file include/roaring/containers/array.h */ +/* begin file include/roaring/containers/bitset.h */ +/* + * bitset.h + * + */ + +#ifndef INCLUDE_CONTAINERS_BITSET_H_ +#define INCLUDE_CONTAINERS_BITSET_H_ + +#include +#include + +#ifdef USEAVX +#define ALIGN_AVX __attribute__((aligned(sizeof(__m256i)))) +#else +#define ALIGN_AVX +#endif + +enum { + BITSET_CONTAINER_SIZE_IN_WORDS = (1 << 16) / 64, + BITSET_UNKNOWN_CARDINALITY = -1 +}; + +struct bitset_container_s { + int32_t cardinality; + uint64_t *array; +}; + +typedef struct bitset_container_s bitset_container_t; + +/* Create a new bitset. Return NULL in case of failure. */ +bitset_container_t *bitset_container_create(void); + +/* Free memory. */ +void bitset_container_free(bitset_container_t *bitset); + +/* Clear bitset (sets bits to 0). */ +void bitset_container_clear(bitset_container_t *bitset); + +/* Set all bits to 1. */ +void bitset_container_set_all(bitset_container_t *bitset); + +/* Duplicate bitset */ +bitset_container_t *bitset_container_clone(const bitset_container_t *src); + +int32_t bitset_container_serialize(const bitset_container_t *container, + char *buf) WARN_UNUSED; + +uint32_t bitset_container_serialization_len(void); + +void *bitset_container_deserialize(const char *buf, size_t buf_len); + +/* Set the bit in [begin,end). WARNING: as of April 2016, this method is slow + * and + * should not be used in performance-sensitive code. Ever. */ +void bitset_container_set_range(bitset_container_t *bitset, uint32_t begin, + uint32_t end); + +#ifdef ASMBITMANIPOPTIMIZATION +/* Set the ith bit. */ +static inline void bitset_container_set(bitset_container_t *bitset, + uint16_t pos) { + uint64_t shift = 6; + uint64_t offset; + uint64_t p = pos; + ASM_SHIFT_RIGHT(p, shift, offset); + uint64_t load = bitset->array[offset]; + ASM_SET_BIT_INC_WAS_CLEAR(load, p, bitset->cardinality); + bitset->array[offset] = load; +} + +/* Unset the ith bit. */ +static inline void bitset_container_unset(bitset_container_t *bitset, + uint16_t pos) { + uint64_t shift = 6; + uint64_t offset; + uint64_t p = pos; + ASM_SHIFT_RIGHT(p, shift, offset); + uint64_t load = bitset->array[offset]; + ASM_CLEAR_BIT_DEC_WAS_SET(load, p, bitset->cardinality); + bitset->array[offset] = load; +} + +/* Add `pos' to `bitset'. Returns true if `pos' was not present. Might be slower + * than bitset_container_set. */ +static inline bool bitset_container_add(bitset_container_t *bitset, + uint16_t pos) { + uint64_t shift = 6; + uint64_t offset; + uint64_t p = pos; + ASM_SHIFT_RIGHT(p, shift, offset); + uint64_t load = bitset->array[offset]; + // could be possibly slightly further optimized + const int32_t oldcard = bitset->cardinality; + ASM_SET_BIT_INC_WAS_CLEAR(load, p, bitset->cardinality); + bitset->array[offset] = load; + return bitset->cardinality - oldcard; +} + +/* Remove `pos' from `bitset'. Returns true if `pos' was present. Might be + * slower than bitset_container_unset. */ +static inline bool bitset_container_remove(bitset_container_t *bitset, + uint16_t pos) { + uint64_t shift = 6; + uint64_t offset; + uint64_t p = pos; + ASM_SHIFT_RIGHT(p, shift, offset); + uint64_t load = bitset->array[offset]; + // could be possibly slightly further optimized + const int32_t oldcard = bitset->cardinality; + ASM_CLEAR_BIT_DEC_WAS_SET(load, p, bitset->cardinality); + bitset->array[offset] = load; + return oldcard - bitset->cardinality; +} + +/* Get the value of the ith bit. */ +static inline bool bitset_container_get(const bitset_container_t *bitset, + uint16_t pos) { + uint64_t word = bitset->array[pos >> 6]; + const uint64_t p = pos; + ASM_INPLACESHIFT_RIGHT(word, p); + return word & 1; +} + +#else + +/* Set the ith bit. */ +static inline void bitset_container_set(bitset_container_t *bitset, + uint16_t pos) { + const uint64_t old_word = bitset->array[pos >> 6]; + const int index = pos & 63; + const uint64_t new_word = old_word | (UINT64_C(1) << index); + bitset->cardinality += (uint32_t)((old_word ^ new_word) >> index); + bitset->array[pos >> 6] = new_word; +} + +/* Unset the ith bit. */ +static inline void bitset_container_unset(bitset_container_t *bitset, + uint16_t pos) { + const uint64_t old_word = bitset->array[pos >> 6]; + const int index = pos & 63; + const uint64_t new_word = old_word & (~(UINT64_C(1) << index)); + bitset->cardinality -= (uint32_t)((old_word ^ new_word) >> index); + bitset->array[pos >> 6] = new_word; +} + +/* Add `pos' to `bitset'. Returns true if `pos' was not present. Might be slower + * than bitset_container_set. */ +static inline bool bitset_container_add(bitset_container_t *bitset, + uint16_t pos) { + const uint64_t old_word = bitset->array[pos >> 6]; + const int index = pos & 63; + const uint64_t new_word = old_word | (UINT64_C(1) << index); + const uint64_t increment = (old_word ^ new_word) >> index; + bitset->cardinality += (uint32_t)increment; + bitset->array[pos >> 6] = new_word; + return increment > 0; +} + +/* Remove `pos' from `bitset'. Returns true if `pos' was present. Might be + * slower than bitset_container_unset. */ +static inline bool bitset_container_remove(bitset_container_t *bitset, + uint16_t pos) { + const uint64_t old_word = bitset->array[pos >> 6]; + const int index = pos & 63; + const uint64_t new_word = old_word & (~(UINT64_C(1) << index)); + const uint64_t increment = (old_word ^ new_word) >> index; + bitset->cardinality -= (uint32_t)increment; + bitset->array[pos >> 6] = new_word; + return increment > 0; +} + +/* Get the value of the ith bit. */ +static inline bool bitset_container_get(const bitset_container_t *bitset, + uint16_t pos) { + const uint64_t word = bitset->array[pos >> 6]; + return (word >> (pos & 63)) & 1; +} + +#endif + +/* +* Check if all bits are set in a range of positions from pos_start (included) to +* pos_end (excluded). +*/ +static inline bool bitset_container_get_range(const bitset_container_t *bitset, + uint32_t pos_start, uint32_t pos_end) { + + const uint32_t start = pos_start >> 6; + const uint32_t end = pos_end >> 6; + + const uint64_t first = ~((1ULL << (pos_start & 0x3F)) - 1); + const uint64_t last = (1ULL << (pos_end & 0x3F)) - 1; + + if (start == end) return ((bitset->array[end] & first & last) == (first & last)); + if ((bitset->array[start] & first) != first) return false; + + if ((end < BITSET_CONTAINER_SIZE_IN_WORDS) && ((bitset->array[end] & last) != last)){ + + return false; + } + + for (uint16_t i = start + 1; (i < BITSET_CONTAINER_SIZE_IN_WORDS) && (i < end); ++i){ + + if (bitset->array[i] != UINT64_C(0xFFFFFFFFFFFFFFFF)) return false; + } + + return true; +} + +/* Check whether `bitset' is present in `array'. Calls bitset_container_get. */ +static inline bool bitset_container_contains(const bitset_container_t *bitset, + uint16_t pos) { + return bitset_container_get(bitset, pos); +} + +/* +* Check whether a range of bits from position `pos_start' (included) to `pos_end' (excluded) +* is present in `bitset'. Calls bitset_container_get_all. +*/ +static inline bool bitset_container_contains_range(const bitset_container_t *bitset, + uint32_t pos_start, uint32_t pos_end) { + return bitset_container_get_range(bitset, pos_start, pos_end); +} + +/* Get the number of bits set */ +static inline int bitset_container_cardinality( + const bitset_container_t *bitset) { + return bitset->cardinality; +} + + + + +/* Copy one container into another. We assume that they are distinct. */ +void bitset_container_copy(const bitset_container_t *source, + bitset_container_t *dest); + +/* Add all the values [min,max) at a distance k*step from min: min, + * min+step,.... */ +void bitset_container_add_from_range(bitset_container_t *bitset, uint32_t min, + uint32_t max, uint16_t step); + +/* Get the number of bits set (force computation). This does not modify bitset. + * To update the cardinality, you should do + * bitset->cardinality = bitset_container_compute_cardinality(bitset).*/ +int bitset_container_compute_cardinality(const bitset_container_t *bitset); + +/* Get whether there is at least one bit set (see bitset_container_empty for the reverse), + when the cardinality is unknown, it is computed and stored in the struct */ +static inline bool bitset_container_nonzero_cardinality( + bitset_container_t *bitset) { + // account for laziness + if (bitset->cardinality == BITSET_UNKNOWN_CARDINALITY) { + // could bail early instead with a nonzero result + bitset->cardinality = bitset_container_compute_cardinality(bitset); + } + return bitset->cardinality > 0; +} + +/* Check whether this bitset is empty (see bitset_container_nonzero_cardinality for the reverse), + * it never modifies the bitset struct. */ +static inline bool bitset_container_empty( + const bitset_container_t *bitset) { + if (bitset->cardinality == BITSET_UNKNOWN_CARDINALITY) { + for (int i = 0; i < BITSET_CONTAINER_SIZE_IN_WORDS; i ++) { + if((bitset->array[i]) != 0) return false; + } + return true; + } + return bitset->cardinality == 0; +} + + +/* Get whether there is at least one bit set (see bitset_container_empty for the reverse), + the bitset is never modified */ +static inline bool bitset_container_const_nonzero_cardinality( + const bitset_container_t *bitset) { + return !bitset_container_empty(bitset); +} + +/* + * Check whether the two bitsets intersect + */ +bool bitset_container_intersect(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the union of bitsets `src_1' and `src_2' into `dst' and return the + * cardinality. */ +int bitset_container_or(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the union of bitsets `src_1' and `src_2' and return the cardinality. + */ +int bitset_container_or_justcard(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the union of bitsets `src_1' and `src_2' into `dst' and return the + * cardinality. Same as bitset_container_or. */ +int bitset_container_union(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the union of bitsets `src_1' and `src_2' and return the + * cardinality. Same as bitset_container_or_justcard. */ +int bitset_container_union_justcard(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the union of bitsets `src_1' and `src_2' into `dst', but does not + * update the cardinality. Provided to optimize chained operations. */ +int bitset_container_or_nocard(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the union of bitsets `src_1' and `src_2' into `dst', but does not + * update the cardinality. Same as bitset_container_or_nocard */ +int bitset_container_union_nocard(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the intersection of bitsets `src_1' and `src_2' into `dst' and + * return the cardinality. */ +int bitset_container_and(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the intersection of bitsets `src_1' and `src_2' and return the + * cardinality. */ +int bitset_container_and_justcard(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the intersection of bitsets `src_1' and `src_2' into `dst' and + * return the cardinality. Same as bitset_container_and. */ +int bitset_container_intersection(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the intersection of bitsets `src_1' and `src_2' and return the + * cardinality. Same as bitset_container_and_justcard. */ +int bitset_container_intersection_justcard(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the intersection of bitsets `src_1' and `src_2' into `dst', but does + * not update the cardinality. Provided to optimize chained operations. */ +int bitset_container_and_nocard(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the intersection of bitsets `src_1' and `src_2' into `dst', but does + * not update the cardinality. Same as bitset_container_and_nocard */ +int bitset_container_intersection_nocard(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the exclusive or of bitsets `src_1' and `src_2' into `dst' and + * return the cardinality. */ +int bitset_container_xor(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the exclusive or of bitsets `src_1' and `src_2' and return the + * cardinality. */ +int bitset_container_xor_justcard(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the exclusive or of bitsets `src_1' and `src_2' into `dst', but does + * not update the cardinality. Provided to optimize chained operations. */ +int bitset_container_xor_nocard(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the and not of bitsets `src_1' and `src_2' into `dst' and return the + * cardinality. */ +int bitset_container_andnot(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Computes the and not of bitsets `src_1' and `src_2' and return the + * cardinality. */ +int bitset_container_andnot_justcard(const bitset_container_t *src_1, + const bitset_container_t *src_2); + +/* Computes the and not or of bitsets `src_1' and `src_2' into `dst', but does + * not update the cardinality. Provided to optimize chained operations. */ +int bitset_container_andnot_nocard(const bitset_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* + * Write out the 16-bit integers contained in this container as a list of 32-bit + * integers using base + * as the starting value (it might be expected that base has zeros in its 16 + * least significant bits). + * The function returns the number of values written. + * The caller is responsible for allocating enough memory in out. + * The out pointer should point to enough memory (the cardinality times 32 + * bits). + */ +int bitset_container_to_uint32_array(void *out, const bitset_container_t *cont, + uint32_t base); + +/* + * Print this container using printf (useful for debugging). + */ +void bitset_container_printf(const bitset_container_t *v); + +/* + * Print this container using printf as a comma-separated list of 32-bit + * integers starting at base. + */ +void bitset_container_printf_as_uint32_array(const bitset_container_t *v, + uint32_t base); + +/** + * Return the serialized size in bytes of a container. + */ +static inline int32_t bitset_container_serialized_size_in_bytes(void) { + return BITSET_CONTAINER_SIZE_IN_WORDS * 8; +} + +/** + * Return the the number of runs. + */ +int bitset_container_number_of_runs(bitset_container_t *b); + +bool bitset_container_iterate(const bitset_container_t *cont, uint32_t base, + roaring_iterator iterator, void *ptr); +bool bitset_container_iterate64(const bitset_container_t *cont, uint32_t base, + roaring_iterator64 iterator, uint64_t high_bits, + void *ptr); + +/** + * Writes the underlying array to buf, outputs how many bytes were written. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes written should be + * bitset_container_size_in_bytes(container). + */ +int32_t bitset_container_write(const bitset_container_t *container, char *buf); + +/** + * Reads the instance from buf, outputs how many bytes were read. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes read should be bitset_container_size_in_bytes(container). + * You need to provide the (known) cardinality. + */ +int32_t bitset_container_read(int32_t cardinality, + bitset_container_t *container, const char *buf); +/** + * Return the serialized size in bytes of a container (see + * bitset_container_write). + * This is meant to be compatible with the Java and Go versions of Roaring and + * assumes + * that the cardinality of the container is already known or can be computed. + */ +static inline int32_t bitset_container_size_in_bytes( + const bitset_container_t *container) { + (void)container; + return BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); +} + +/** + * Return true if the two containers have the same content. + */ +bool bitset_container_equals(const bitset_container_t *container1, + const bitset_container_t *container2); + +/** +* Return true if container1 is a subset of container2. +*/ +bool bitset_container_is_subset(const bitset_container_t *container1, + const bitset_container_t *container2); + +/** + * If the element of given rank is in this container, supposing that the first + * element has rank start_rank, then the function returns true and sets element + * accordingly. + * Otherwise, it returns false and update start_rank. + */ +bool bitset_container_select(const bitset_container_t *container, + uint32_t *start_rank, uint32_t rank, + uint32_t *element); + +/* Returns the smallest value (assumes not empty) */ +uint16_t bitset_container_minimum(const bitset_container_t *container); + +/* Returns the largest value (assumes not empty) */ +uint16_t bitset_container_maximum(const bitset_container_t *container); + +/* Returns the number of values equal or smaller than x */ +int bitset_container_rank(const bitset_container_t *container, uint16_t x); + +/* Returns the index of the first value equal or larger than x, or -1 */ +int bitset_container_index_equalorlarger(const bitset_container_t *container, uint16_t x); +#endif /* INCLUDE_CONTAINERS_BITSET_H_ */ +/* end file include/roaring/containers/bitset.h */ +/* begin file include/roaring/containers/run.h */ +/* + * run.h + * + */ + +#ifndef INCLUDE_CONTAINERS_RUN_H_ +#define INCLUDE_CONTAINERS_RUN_H_ + +#include +#include +#include +#include + + +/* struct rle16_s - run length pair + * + * @value: start position of the run + * @length: length of the run is `length + 1` + * + * An RLE pair {v, l} would represent the integers between the interval + * [v, v+l+1], e.g. {3, 2} = [3, 4, 5]. + */ +struct rle16_s { + uint16_t value; + uint16_t length; +}; + +typedef struct rle16_s rle16_t; + +/* struct run_container_s - run container bitmap + * + * @n_runs: number of rle_t pairs in `runs`. + * @capacity: capacity in rle_t pairs `runs` can hold. + * @runs: pairs of rle_t. + * + */ +struct run_container_s { + int32_t n_runs; + int32_t capacity; + rle16_t *runs; +}; + +typedef struct run_container_s run_container_t; + +/* Create a new run container. Return NULL in case of failure. */ +run_container_t *run_container_create(void); + +/* Create a new run container with given capacity. Return NULL in case of + * failure. */ +run_container_t *run_container_create_given_capacity(int32_t size); + +/* + * Shrink the capacity to the actual size, return the number of bytes saved. + */ +int run_container_shrink_to_fit(run_container_t *src); + +/* Free memory owned by `run'. */ +void run_container_free(run_container_t *run); + +/* Duplicate container */ +run_container_t *run_container_clone(const run_container_t *src); + +int32_t run_container_serialize(const run_container_t *container, + char *buf) WARN_UNUSED; + +uint32_t run_container_serialization_len(const run_container_t *container); + +void *run_container_deserialize(const char *buf, size_t buf_len); + +/* + * Effectively deletes the value at index index, repacking data. + */ +static inline void recoverRoomAtIndex(run_container_t *run, uint16_t index) { + memmove(run->runs + index, run->runs + (1 + index), + (run->n_runs - index - 1) * sizeof(rle16_t)); + run->n_runs--; +} + +/** + * Good old binary search through rle data + */ +static inline int32_t interleavedBinarySearch(const rle16_t *array, int32_t lenarray, + uint16_t ikey) { + int32_t low = 0; + int32_t high = lenarray - 1; + while (low <= high) { + int32_t middleIndex = (low + high) >> 1; + uint16_t middleValue = array[middleIndex].value; + if (middleValue < ikey) { + low = middleIndex + 1; + } else if (middleValue > ikey) { + high = middleIndex - 1; + } else { + return middleIndex; + } + } + return -(low + 1); +} + +/* + * Returns index of the run which contains $ikey + */ +static inline int32_t rle16_find_run(const rle16_t *array, int32_t lenarray, + uint16_t ikey) { + int32_t low = 0; + int32_t high = lenarray - 1; + while (low <= high) { + int32_t middleIndex = (low + high) >> 1; + uint16_t min = array[middleIndex].value; + uint16_t max = array[middleIndex].value + array[middleIndex].length; + if (ikey > max) { + low = middleIndex + 1; + } else if (ikey < min) { + high = middleIndex - 1; + } else { + return middleIndex; + } + } + return -(low + 1); +} + + +/** + * Returns number of runs which can'be be merged with the key because they + * are less than the key. + * Note that [5,6,7,8] can be merged with the key 9 and won't be counted. + */ +static inline int32_t rle16_count_less(const rle16_t* array, int32_t lenarray, + uint16_t key) { + if (lenarray == 0) return 0; + int32_t low = 0; + int32_t high = lenarray - 1; + while (low <= high) { + int32_t middleIndex = (low + high) >> 1; + uint16_t min_value = array[middleIndex].value; + uint16_t max_value = array[middleIndex].value + array[middleIndex].length; + if (max_value + UINT32_C(1) < key) { // uint32 arithmetic + low = middleIndex + 1; + } else if (key < min_value) { + high = middleIndex - 1; + } else { + return middleIndex; + } + } + return low; +} + +static inline int32_t rle16_count_greater(const rle16_t* array, int32_t lenarray, + uint16_t key) { + if (lenarray == 0) return 0; + int32_t low = 0; + int32_t high = lenarray - 1; + while (low <= high) { + int32_t middleIndex = (low + high) >> 1; + uint16_t min_value = array[middleIndex].value; + uint16_t max_value = array[middleIndex].value + array[middleIndex].length; + if (max_value < key) { + low = middleIndex + 1; + } else if (key + UINT32_C(1) < min_value) { // uint32 arithmetic + high = middleIndex - 1; + } else { + return lenarray - (middleIndex + 1); + } + } + return lenarray - low; +} + +/** + * increase capacity to at least min. Whether the + * existing data needs to be copied over depends on copy. If "copy" is false, + * then the new content will be uninitialized, otherwise a copy is made. + */ +void run_container_grow(run_container_t *run, int32_t min, bool copy); + +/** + * Moves the data so that we can write data at index + */ +static inline void makeRoomAtIndex(run_container_t *run, uint16_t index) { + /* This function calls realloc + memmove sequentially to move by one index. + * Potentially copying twice the array. + */ + if (run->n_runs + 1 > run->capacity) + run_container_grow(run, run->n_runs + 1, true); + memmove(run->runs + 1 + index, run->runs + index, + (run->n_runs - index) * sizeof(rle16_t)); + run->n_runs++; +} + +/* Add `pos' to `run'. Returns true if `pos' was not present. */ +bool run_container_add(run_container_t *run, uint16_t pos); + +/* Remove `pos' from `run'. Returns true if `pos' was present. */ +static inline bool run_container_remove(run_container_t *run, uint16_t pos) { + int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos); + if (index >= 0) { + int32_t le = run->runs[index].length; + if (le == 0) { + recoverRoomAtIndex(run, (uint16_t)index); + } else { + run->runs[index].value++; + run->runs[index].length--; + } + return true; + } + index = -index - 2; // points to preceding value, possibly -1 + if (index >= 0) { // possible match + int32_t offset = pos - run->runs[index].value; + int32_t le = run->runs[index].length; + if (offset < le) { + // need to break in two + run->runs[index].length = (uint16_t)(offset - 1); + // need to insert + uint16_t newvalue = pos + 1; + int32_t newlength = le - offset - 1; + makeRoomAtIndex(run, (uint16_t)(index + 1)); + run->runs[index + 1].value = newvalue; + run->runs[index + 1].length = (uint16_t)newlength; + return true; + + } else if (offset == le) { + run->runs[index].length--; + return true; + } + } + // no match + return false; +} + +/* Check whether `pos' is present in `run'. */ +static inline bool run_container_contains(const run_container_t *run, uint16_t pos) { + int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos); + if (index >= 0) return true; + index = -index - 2; // points to preceding value, possibly -1 + if (index != -1) { // possible match + int32_t offset = pos - run->runs[index].value; + int32_t le = run->runs[index].length; + if (offset <= le) return true; + } + return false; +} + +/* +* Check whether all positions in a range of positions from pos_start (included) +* to pos_end (excluded) is present in `run'. +*/ +static inline bool run_container_contains_range(const run_container_t *run, + uint32_t pos_start, uint32_t pos_end) { + uint32_t count = 0; + int32_t index = interleavedBinarySearch(run->runs, run->n_runs, pos_start); + if (index < 0) { + index = -index - 2; + if ((index == -1) || ((pos_start - run->runs[index].value) > run->runs[index].length)){ + return false; + } + } + for (int32_t i = index; i < run->n_runs; ++i) { + const uint32_t stop = run->runs[i].value + run->runs[i].length; + if (run->runs[i].value >= pos_end) break; + if (stop >= pos_end) { + count += (((pos_end - run->runs[i].value) > 0) ? (pos_end - run->runs[i].value) : 0); + break; + } + const uint32_t min = (stop - pos_start) > 0 ? (stop - pos_start) : 0; + count += (min < run->runs[i].length) ? min : run->runs[i].length; + } + return count >= (pos_end - pos_start - 1); +} + +#ifdef USEAVX + +/* Get the cardinality of `run'. Requires an actual computation. */ +static inline int run_container_cardinality(const run_container_t *run) { + const int32_t n_runs = run->n_runs; + const rle16_t *runs = run->runs; + + /* by initializing with n_runs, we omit counting the +1 for each pair. */ + int sum = n_runs; + int32_t k = 0; + const int32_t step = sizeof(__m256i) / sizeof(rle16_t); + if (n_runs > step) { + __m256i total = _mm256_setzero_si256(); + for (; k + step <= n_runs; k += step) { + __m256i ymm1 = _mm256_lddqu_si256((const __m256i *)(runs + k)); + __m256i justlengths = _mm256_srli_epi32(ymm1, 16); + total = _mm256_add_epi32(total, justlengths); + } + // a store might be faster than extract? + uint32_t buffer[sizeof(__m256i) / sizeof(rle16_t)]; + _mm256_storeu_si256((__m256i *)buffer, total); + sum += (buffer[0] + buffer[1]) + (buffer[2] + buffer[3]) + + (buffer[4] + buffer[5]) + (buffer[6] + buffer[7]); + } + for (; k < n_runs; ++k) { + sum += runs[k].length; + } + + return sum; +} + +#else + +/* Get the cardinality of `run'. Requires an actual computation. */ +static inline int run_container_cardinality(const run_container_t *run) { + const int32_t n_runs = run->n_runs; + const rle16_t *runs = run->runs; + + /* by initializing with n_runs, we omit counting the +1 for each pair. */ + int sum = n_runs; + for (int k = 0; k < n_runs; ++k) { + sum += runs[k].length; + } + + return sum; +} +#endif + +/* Card > 0?, see run_container_empty for the reverse */ +static inline bool run_container_nonzero_cardinality( + const run_container_t *run) { + return run->n_runs > 0; // runs never empty +} + +/* Card == 0?, see run_container_nonzero_cardinality for the reverse */ +static inline bool run_container_empty( + const run_container_t *run) { + return run->n_runs == 0; // runs never empty +} + + + +/* Copy one container into another. We assume that they are distinct. */ +void run_container_copy(const run_container_t *src, run_container_t *dst); + +/* Set the cardinality to zero (does not release memory). */ +static inline void run_container_clear(run_container_t *run) { + run->n_runs = 0; +} + +/** + * Append run described by vl to the run container, possibly merging. + * It is assumed that the run would be inserted at the end of the container, no + * check is made. + * It is assumed that the run container has the necessary capacity: caller is + * responsible for checking memory capacity. + * + * + * This is not a safe function, it is meant for performance: use with care. + */ +static inline void run_container_append(run_container_t *run, rle16_t vl, + rle16_t *previousrl) { + const uint32_t previousend = previousrl->value + previousrl->length; + if (vl.value > previousend + 1) { // we add a new one + run->runs[run->n_runs] = vl; + run->n_runs++; + *previousrl = vl; + } else { + uint32_t newend = vl.value + vl.length + UINT32_C(1); + if (newend > previousend) { // we merge + previousrl->length = (uint16_t)(newend - 1 - previousrl->value); + run->runs[run->n_runs - 1] = *previousrl; + } + } +} + +/** + * Like run_container_append but it is assumed that the content of run is empty. + */ +static inline rle16_t run_container_append_first(run_container_t *run, + rle16_t vl) { + run->runs[run->n_runs] = vl; + run->n_runs++; + return vl; +} + +/** + * append a single value given by val to the run container, possibly merging. + * It is assumed that the value would be inserted at the end of the container, + * no check is made. + * It is assumed that the run container has the necessary capacity: caller is + * responsible for checking memory capacity. + * + * This is not a safe function, it is meant for performance: use with care. + */ +static inline void run_container_append_value(run_container_t *run, + uint16_t val, + rle16_t *previousrl) { + const uint32_t previousend = previousrl->value + previousrl->length; + if (val > previousend + 1) { // we add a new one + //*previousrl = (rle16_t){.value = val, .length = 0};// requires C99 + previousrl->value = val; + previousrl->length = 0; + + run->runs[run->n_runs] = *previousrl; + run->n_runs++; + } else if (val == previousend + 1) { // we merge + previousrl->length++; + run->runs[run->n_runs - 1] = *previousrl; + } +} + +/** + * Like run_container_append_value but it is assumed that the content of run is + * empty. + */ +static inline rle16_t run_container_append_value_first(run_container_t *run, + uint16_t val) { + // rle16_t newrle = (rle16_t){.value = val, .length = 0};// requires C99 + rle16_t newrle; + newrle.value = val; + newrle.length = 0; + + run->runs[run->n_runs] = newrle; + run->n_runs++; + return newrle; +} + +/* Check whether the container spans the whole chunk (cardinality = 1<<16). + * This check can be done in constant time (inexpensive). */ +static inline bool run_container_is_full(const run_container_t *run) { + rle16_t vl = run->runs[0]; + return (run->n_runs == 1) && (vl.value == 0) && (vl.length == 0xFFFF); +} + +/* Compute the union of `src_1' and `src_2' and write the result to `dst' + * It is assumed that `dst' is distinct from both `src_1' and `src_2'. */ +void run_container_union(const run_container_t *src_1, + const run_container_t *src_2, run_container_t *dst); + +/* Compute the union of `src_1' and `src_2' and write the result to `src_1' */ +void run_container_union_inplace(run_container_t *src_1, + const run_container_t *src_2); + +/* Compute the intersection of src_1 and src_2 and write the result to + * dst. It is assumed that dst is distinct from both src_1 and src_2. */ +void run_container_intersection(const run_container_t *src_1, + const run_container_t *src_2, + run_container_t *dst); + +/* Compute the size of the intersection of src_1 and src_2 . */ +int run_container_intersection_cardinality(const run_container_t *src_1, + const run_container_t *src_2); + +/* Check whether src_1 and src_2 intersect. */ +bool run_container_intersect(const run_container_t *src_1, + const run_container_t *src_2); + +/* Compute the symmetric difference of `src_1' and `src_2' and write the result + * to `dst' + * It is assumed that `dst' is distinct from both `src_1' and `src_2'. */ +void run_container_xor(const run_container_t *src_1, + const run_container_t *src_2, run_container_t *dst); + +/* + * Write out the 16-bit integers contained in this container as a list of 32-bit + * integers using base + * as the starting value (it might be expected that base has zeros in its 16 + * least significant bits). + * The function returns the number of values written. + * The caller is responsible for allocating enough memory in out. + */ +int run_container_to_uint32_array(void *vout, const run_container_t *cont, + uint32_t base); + +/* + * Print this container using printf (useful for debugging). + */ +void run_container_printf(const run_container_t *v); + +/* + * Print this container using printf as a comma-separated list of 32-bit + * integers starting at base. + */ +void run_container_printf_as_uint32_array(const run_container_t *v, + uint32_t base); + +/** + * Return the serialized size in bytes of a container having "num_runs" runs. + */ +static inline int32_t run_container_serialized_size_in_bytes(int32_t num_runs) { + return sizeof(uint16_t) + + sizeof(rle16_t) * num_runs; // each run requires 2 2-byte entries. +} + +bool run_container_iterate(const run_container_t *cont, uint32_t base, + roaring_iterator iterator, void *ptr); +bool run_container_iterate64(const run_container_t *cont, uint32_t base, + roaring_iterator64 iterator, uint64_t high_bits, + void *ptr); + +/** + * Writes the underlying array to buf, outputs how many bytes were written. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes written should be run_container_size_in_bytes(container). + */ +int32_t run_container_write(const run_container_t *container, char *buf); + +/** + * Reads the instance from buf, outputs how many bytes were read. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes read should be bitset_container_size_in_bytes(container). + * The cardinality parameter is provided for consistency with other containers, + * but + * it might be effectively ignored.. + */ +int32_t run_container_read(int32_t cardinality, run_container_t *container, + const char *buf); + +/** + * Return the serialized size in bytes of a container (see run_container_write). + * This is meant to be compatible with the Java and Go versions of Roaring. + */ +static inline int32_t run_container_size_in_bytes( + const run_container_t *container) { + return run_container_serialized_size_in_bytes(container->n_runs); +} + +/** + * Return true if the two containers have the same content. + */ +static inline bool run_container_equals(const run_container_t *container1, + const run_container_t *container2) { + if (container1->n_runs != container2->n_runs) { + return false; + } + return memequals(container1->runs, container2->runs, + container1->n_runs * sizeof(rle16_t)); +} + +/** +* Return true if container1 is a subset of container2. +*/ +bool run_container_is_subset(const run_container_t *container1, + const run_container_t *container2); + +/** + * Used in a start-finish scan that appends segments, for XOR and NOT + */ + +void run_container_smart_append_exclusive(run_container_t *src, + const uint16_t start, + const uint16_t length); + +/** +* The new container consists of a single run [start,stop). +* It is required that stop>start, the caller is responsibility for this check. +* It is required that stop <= (1<<16), the caller is responsibility for this check. +* The cardinality of the created container is stop - start. +* Returns NULL on failure +*/ +static inline run_container_t *run_container_create_range(uint32_t start, + uint32_t stop) { + run_container_t *rc = run_container_create_given_capacity(1); + if (rc) { + rle16_t r; + r.value = (uint16_t)start; + r.length = (uint16_t)(stop - start - 1); + run_container_append_first(rc, r); + } + return rc; +} + +/** + * If the element of given rank is in this container, supposing that the first + * element has rank start_rank, then the function returns true and sets element + * accordingly. + * Otherwise, it returns false and update start_rank. + */ +bool run_container_select(const run_container_t *container, + uint32_t *start_rank, uint32_t rank, + uint32_t *element); + +/* Compute the difference of src_1 and src_2 and write the result to + * dst. It is assumed that dst is distinct from both src_1 and src_2. */ + +void run_container_andnot(const run_container_t *src_1, + const run_container_t *src_2, run_container_t *dst); + +/* Returns the smallest value (assumes not empty) */ +static inline uint16_t run_container_minimum(const run_container_t *run) { + if (run->n_runs == 0) return 0; + return run->runs[0].value; +} + +/* Returns the largest value (assumes not empty) */ +static inline uint16_t run_container_maximum(const run_container_t *run) { + if (run->n_runs == 0) return 0; + return run->runs[run->n_runs - 1].value + run->runs[run->n_runs - 1].length; +} + +/* Returns the number of values equal or smaller than x */ +int run_container_rank(const run_container_t *arr, uint16_t x); + +/* Returns the index of the first run containing a value at least as large as x, or -1 */ +static inline int run_container_index_equalorlarger(const run_container_t *arr, uint16_t x) { + int32_t index = interleavedBinarySearch(arr->runs, arr->n_runs, x); + if (index >= 0) return index; + index = -index - 2; // points to preceding run, possibly -1 + if (index != -1) { // possible match + int32_t offset = x - arr->runs[index].value; + int32_t le = arr->runs[index].length; + if (offset <= le) return index; + } + index += 1; + if(index < arr->n_runs) { + return index; + } + return -1; +} + +/* + * Add all values in range [min, max] using hint. + */ +static inline void run_container_add_range_nruns(run_container_t* run, + uint32_t min, uint32_t max, + int32_t nruns_less, + int32_t nruns_greater) { + int32_t nruns_common = run->n_runs - nruns_less - nruns_greater; + if (nruns_common == 0) { + makeRoomAtIndex(run, nruns_less); + run->runs[nruns_less].value = min; + run->runs[nruns_less].length = max - min; + } else { + uint32_t common_min = run->runs[nruns_less].value; + uint32_t common_max = run->runs[nruns_less + nruns_common - 1].value + + run->runs[nruns_less + nruns_common - 1].length; + uint32_t result_min = (common_min < min) ? common_min : min; + uint32_t result_max = (common_max > max) ? common_max : max; + + run->runs[nruns_less].value = result_min; + run->runs[nruns_less].length = result_max - result_min; + + memmove(&(run->runs[nruns_less + 1]), + &(run->runs[run->n_runs - nruns_greater]), + nruns_greater*sizeof(rle16_t)); + run->n_runs = nruns_less + 1 + nruns_greater; + } +} + +/** + * Add all values in range [min, max] + */ +static inline void run_container_add_range(run_container_t* run, + uint32_t min, uint32_t max) { + int32_t nruns_greater = rle16_count_greater(run->runs, run->n_runs, max); + int32_t nruns_less = rle16_count_less(run->runs, run->n_runs - nruns_greater, min); + run_container_add_range_nruns(run, min, max, nruns_less, nruns_greater); +} + +/** + * Shifts last $count elements either left (distance < 0) or right (distance > 0) + */ +static inline void run_container_shift_tail(run_container_t* run, + int32_t count, int32_t distance) { + if (distance > 0) { + if (run->capacity < count+distance) { + run_container_grow(run, count+distance, true); + } + } + int32_t srcpos = run->n_runs - count; + int32_t dstpos = srcpos + distance; + memmove(&(run->runs[dstpos]), &(run->runs[srcpos]), sizeof(rle16_t) * count); + run->n_runs += distance; +} + +/** + * Remove all elements in range [min, max] + */ +static inline void run_container_remove_range(run_container_t *run, uint32_t min, uint32_t max) { + int32_t first = rle16_find_run(run->runs, run->n_runs, min); + int32_t last = rle16_find_run(run->runs, run->n_runs, max); + + if (first >= 0 && min > run->runs[first].value && + max < ((uint32_t)run->runs[first].value + (uint32_t)run->runs[first].length)) { + // split this run into two adjacent runs + + // right subinterval + makeRoomAtIndex(run, first+1); + run->runs[first+1].value = max + 1; + run->runs[first+1].length = (run->runs[first].value + run->runs[first].length) - (max + 1); + + // left subinterval + run->runs[first].length = (min - 1) - run->runs[first].value; + + return; + } + + // update left-most partial run + if (first >= 0) { + if (min > run->runs[first].value) { + run->runs[first].length = (min - 1) - run->runs[first].value; + first++; + } + } else { + first = -first-1; + } + + // update right-most run + if (last >= 0) { + uint16_t run_max = run->runs[last].value + run->runs[last].length; + if (run_max > max) { + run->runs[last].value = max + 1; + run->runs[last].length = run_max - (max + 1); + last--; + } + } else { + last = (-last-1) - 1; + } + + // remove intermediate runs + if (first <= last) { + run_container_shift_tail(run, run->n_runs - (last+1), -(last-first+1)); + } +} + + +#endif /* INCLUDE_CONTAINERS_RUN_H_ */ +/* end file include/roaring/containers/run.h */ +/* begin file include/roaring/containers/convert.h */ +/* + * convert.h + * + */ + +#ifndef INCLUDE_CONTAINERS_CONVERT_H_ +#define INCLUDE_CONTAINERS_CONVERT_H_ + + +/* Convert an array into a bitset. The input container is not freed or modified. + */ +bitset_container_t *bitset_container_from_array(const array_container_t *arr); + +/* Convert a run into a bitset. The input container is not freed or modified. */ +bitset_container_t *bitset_container_from_run(const run_container_t *arr); + +/* Convert a run into an array. The input container is not freed or modified. */ +array_container_t *array_container_from_run(const run_container_t *arr); + +/* Convert a bitset into an array. The input container is not freed or modified. + */ +array_container_t *array_container_from_bitset(const bitset_container_t *bits); + +/* Convert an array into a run. The input container is not freed or modified. + */ +run_container_t *run_container_from_array(const array_container_t *c); + +/* convert a run into either an array or a bitset + * might free the container. This does not free the input run container. */ +void *convert_to_bitset_or_array_container(run_container_t *r, int32_t card, + uint8_t *resulttype); + +/* convert containers to and from runcontainers, as is most space efficient. + * The container might be freed. */ +void *convert_run_optimize(void *c, uint8_t typecode_original, + uint8_t *typecode_after); + +/* converts a run container to either an array or a bitset, IF it saves space. + */ +/* If a conversion occurs, the caller is responsible to free the original + * container and + * he becomes responsible to free the new one. */ +void *convert_run_to_efficient_container(run_container_t *c, + uint8_t *typecode_after); +// like convert_run_to_efficient_container but frees the old result if needed +void *convert_run_to_efficient_container_and_free(run_container_t *c, + uint8_t *typecode_after); + +/** + * Create new bitset container which is a union of run container and + * range [min, max]. Caller is responsible for freeing run container. + */ +bitset_container_t *bitset_container_from_run_range(const run_container_t *run, + uint32_t min, uint32_t max); + +#endif /* INCLUDE_CONTAINERS_CONVERT_H_ */ +/* end file include/roaring/containers/convert.h */ +/* begin file include/roaring/containers/mixed_equal.h */ +/* + * mixed_equal.h + * + */ + +#ifndef CONTAINERS_MIXED_EQUAL_H_ +#define CONTAINERS_MIXED_EQUAL_H_ + + +/** + * Return true if the two containers have the same content. + */ +bool array_container_equal_bitset(const array_container_t* container1, + const bitset_container_t* container2); + +/** + * Return true if the two containers have the same content. + */ +bool run_container_equals_array(const run_container_t* container1, + const array_container_t* container2); +/** + * Return true if the two containers have the same content. + */ +bool run_container_equals_bitset(const run_container_t* container1, + const bitset_container_t* container2); + +#endif /* CONTAINERS_MIXED_EQUAL_H_ */ +/* end file include/roaring/containers/mixed_equal.h */ +/* begin file include/roaring/containers/mixed_subset.h */ +/* + * mixed_subset.h + * + */ + +#ifndef CONTAINERS_MIXED_SUBSET_H_ +#define CONTAINERS_MIXED_SUBSET_H_ + + +/** + * Return true if container1 is a subset of container2. + */ +bool array_container_is_subset_bitset(const array_container_t* container1, + const bitset_container_t* container2); + +/** +* Return true if container1 is a subset of container2. + */ +bool run_container_is_subset_array(const run_container_t* container1, + const array_container_t* container2); + +/** +* Return true if container1 is a subset of container2. + */ +bool array_container_is_subset_run(const array_container_t* container1, + const run_container_t* container2); + +/** +* Return true if container1 is a subset of container2. + */ +bool run_container_is_subset_bitset(const run_container_t* container1, + const bitset_container_t* container2); + +/** +* Return true if container1 is a subset of container2. +*/ +bool bitset_container_is_subset_run(const bitset_container_t* container1, + const run_container_t* container2); + +#endif /* CONTAINERS_MIXED_SUBSET_H_ */ +/* end file include/roaring/containers/mixed_subset.h */ +/* begin file include/roaring/containers/mixed_andnot.h */ +/* + * mixed_andnot.h + */ +#ifndef INCLUDE_CONTAINERS_MIXED_ANDNOT_H_ +#define INCLUDE_CONTAINERS_MIXED_ANDNOT_H_ + + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst, a valid array container that could be the same as dst.*/ +void array_bitset_container_andnot(const array_container_t *src_1, + const bitset_container_t *src_2, + array_container_t *dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * src_1 */ + +void array_bitset_container_iandnot(array_container_t *src_1, + const bitset_container_t *src_2); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst, which does not initially have a valid container. + * Return true for a bitset result; false for array + */ + +bool bitset_array_container_andnot(const bitset_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_array_container_iandnot(bitset_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_andnot(const run_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_iandnot(run_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool bitset_run_container_andnot(const bitset_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_run_container_iandnot(bitset_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* dst does not indicate a valid container initially. Eventually it + * can become any type of container. + */ + +int run_array_container_andnot(const run_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +int run_array_container_iandnot(run_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* dst must be a valid array container, allowed to be src_1 */ + +void array_run_container_andnot(const array_container_t *src_1, + const run_container_t *src_2, + array_container_t *dst); + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +void array_run_container_iandnot(array_container_t *src_1, + const run_container_t *src_2); + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int run_run_container_andnot(const run_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +int run_run_container_iandnot(run_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* + * dst is a valid array container and may be the same as src_1 + */ + +void array_array_container_andnot(const array_container_t *src_1, + const array_container_t *src_2, + array_container_t *dst); + +/* inplace array-array andnot will always be able to reuse the space of + * src_1 */ +void array_array_container_iandnot(array_container_t *src_1, + const array_container_t *src_2); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). Return value is + * "dst is a bitset" + */ + +bool bitset_bitset_container_andnot(const bitset_container_t *src_1, + const bitset_container_t *src_2, + void **dst); + +/* Compute the andnot of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_bitset_container_iandnot(bitset_container_t *src_1, + const bitset_container_t *src_2, + void **dst); +#endif +/* end file include/roaring/containers/mixed_andnot.h */ +/* begin file include/roaring/containers/mixed_intersection.h */ +/* + * mixed_intersection.h + * + */ + +#ifndef INCLUDE_CONTAINERS_MIXED_INTERSECTION_H_ +#define INCLUDE_CONTAINERS_MIXED_INTERSECTION_H_ + +/* These functions appear to exclude cases where the + * inputs have the same type and the output is guaranteed + * to have the same type as the inputs. Eg, array intersection + */ + + +/* Compute the intersection of src_1 and src_2 and write the result to + * dst. It is allowed for dst to be equal to src_1. We assume that dst is a + * valid container. */ +void array_bitset_container_intersection(const array_container_t *src_1, + const bitset_container_t *src_2, + array_container_t *dst); + +/* Compute the size of the intersection of src_1 and src_2. */ +int array_bitset_container_intersection_cardinality( + const array_container_t *src_1, const bitset_container_t *src_2); + + + +/* Checking whether src_1 and src_2 intersect. */ +bool array_bitset_container_intersect(const array_container_t *src_1, + const bitset_container_t *src_2); + +/* + * Compute the intersection between src_1 and src_2 and write the result + * to *dst. If the return function is true, the result is a bitset_container_t + * otherwise is a array_container_t. We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +bool bitset_bitset_container_intersection(const bitset_container_t *src_1, + const bitset_container_t *src_2, + void **dst); + +/* Compute the intersection between src_1 and src_2 and write the result to + * dst. It is allowed for dst to be equal to src_1. We assume that dst is a + * valid container. */ +void array_run_container_intersection(const array_container_t *src_1, + const run_container_t *src_2, + array_container_t *dst); + +/* Compute the intersection between src_1 and src_2 and write the result to + * *dst. If the result is true then the result is a bitset_container_t + * otherwise is a array_container_t. + * If *dst == src_2, then an in-place intersection is attempted + **/ +bool run_bitset_container_intersection(const run_container_t *src_1, + const bitset_container_t *src_2, + void **dst); + +/* Compute the size of the intersection between src_1 and src_2 . */ +int array_run_container_intersection_cardinality(const array_container_t *src_1, + const run_container_t *src_2); + +/* Compute the size of the intersection between src_1 and src_2 + **/ +int run_bitset_container_intersection_cardinality(const run_container_t *src_1, + const bitset_container_t *src_2); + + +/* Check that src_1 and src_2 intersect. */ +bool array_run_container_intersect(const array_container_t *src_1, + const run_container_t *src_2); + +/* Check that src_1 and src_2 intersect. + **/ +bool run_bitset_container_intersect(const run_container_t *src_1, + const bitset_container_t *src_2); + +/* + * Same as bitset_bitset_container_intersection except that if the output is to + * be a + * bitset_container_t, then src_1 is modified and no allocation is made. + * If the output is to be an array_container_t, then caller is responsible + * to free the container. + * In all cases, the result is in *dst. + */ +bool bitset_bitset_container_intersection_inplace( + bitset_container_t *src_1, const bitset_container_t *src_2, void **dst); + +#endif /* INCLUDE_CONTAINERS_MIXED_INTERSECTION_H_ */ +/* end file include/roaring/containers/mixed_intersection.h */ +/* begin file include/roaring/containers/mixed_negation.h */ +/* + * mixed_negation.h + * + */ + +#ifndef INCLUDE_CONTAINERS_MIXED_NEGATION_H_ +#define INCLUDE_CONTAINERS_MIXED_NEGATION_H_ + + +/* Negation across the entire range of the container. + * Compute the negation of src and write the result + * to *dst. The complement of a + * sufficiently sparse set will always be dense and a hence a bitmap + * We assume that dst is pre-allocated and a valid bitset container + * There can be no in-place version. + */ +void array_container_negation(const array_container_t *src, + bitset_container_t *dst); + +/* Negation across the entire range of the container + * Compute the negation of src and write the result + * to *dst. A true return value indicates a bitset result, + * otherwise the result is an array container. + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +bool bitset_container_negation(const bitset_container_t *src, void **dst); + +/* inplace version */ +/* + * Same as bitset_container_negation except that if the output is to + * be a + * bitset_container_t, then src is modified and no allocation is made. + * If the output is to be an array_container_t, then caller is responsible + * to free the container. + * In all cases, the result is in *dst. + */ +bool bitset_container_negation_inplace(bitset_container_t *src, void **dst); + +/* Negation across the entire range of container + * Compute the negation of src and write the result + * to *dst. + * Return values are the *_TYPECODES as defined * in containers.h + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +int run_container_negation(const run_container_t *src, void **dst); + +/* + * Same as run_container_negation except that if the output is to + * be a + * run_container_t, and has the capacity to hold the result, + * then src is modified and no allocation is made. + * In all cases, the result is in *dst. + */ +int run_container_negation_inplace(run_container_t *src, void **dst); + +/* Negation across a range of the container. + * Compute the negation of src and write the result + * to *dst. Returns true if the result is a bitset container + * and false for an array container. *dst is not preallocated. + */ +bool array_container_negation_range(const array_container_t *src, + const int range_start, const int range_end, + void **dst); + +/* Even when the result would fit, it is unclear how to make an + * inplace version without inefficient copying. Thus this routine + * may be a wrapper for the non-in-place version + */ +bool array_container_negation_range_inplace(array_container_t *src, + const int range_start, + const int range_end, void **dst); + +/* Negation across a range of the container + * Compute the negation of src and write the result + * to *dst. A true return value indicates a bitset result, + * otherwise the result is an array container. + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +bool bitset_container_negation_range(const bitset_container_t *src, + const int range_start, const int range_end, + void **dst); + +/* inplace version */ +/* + * Same as bitset_container_negation except that if the output is to + * be a + * bitset_container_t, then src is modified and no allocation is made. + * If the output is to be an array_container_t, then caller is responsible + * to free the container. + * In all cases, the result is in *dst. + */ +bool bitset_container_negation_range_inplace(bitset_container_t *src, + const int range_start, + const int range_end, void **dst); + +/* Negation across a range of container + * Compute the negation of src and write the result + * to *dst. Return values are the *_TYPECODES as defined * in containers.h + * We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +int run_container_negation_range(const run_container_t *src, + const int range_start, const int range_end, + void **dst); + +/* + * Same as run_container_negation except that if the output is to + * be a + * run_container_t, and has the capacity to hold the result, + * then src is modified and no allocation is made. + * In all cases, the result is in *dst. + */ +int run_container_negation_range_inplace(run_container_t *src, + const int range_start, + const int range_end, void **dst); + +#endif /* INCLUDE_CONTAINERS_MIXED_NEGATION_H_ */ +/* end file include/roaring/containers/mixed_negation.h */ +/* begin file include/roaring/containers/mixed_union.h */ +/* + * mixed_intersection.h + * + */ + +#ifndef INCLUDE_CONTAINERS_MIXED_UNION_H_ +#define INCLUDE_CONTAINERS_MIXED_UNION_H_ + +/* These functions appear to exclude cases where the + * inputs have the same type and the output is guaranteed + * to have the same type as the inputs. Eg, bitset unions + */ + + +/* Compute the union of src_1 and src_2 and write the result to + * dst. It is allowed for src_2 to be dst. */ +void array_bitset_container_union(const array_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Compute the union of src_1 and src_2 and write the result to + * dst. It is allowed for src_2 to be dst. This version does not + * update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). */ +void array_bitset_container_lazy_union(const array_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* + * Compute the union between src_1 and src_2 and write the result + * to *dst. If the return function is true, the result is a bitset_container_t + * otherwise is a array_container_t. We assume that dst is not pre-allocated. In + * case of failure, *dst will be NULL. + */ +bool array_array_container_union(const array_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* + * Compute the union between src_1 and src_2 and write the result + * to *dst if it cannot be written to src_1. If the return function is true, + * the result is a bitset_container_t + * otherwise is a array_container_t. When the result is an array_container_t, it + * it either written to src_1 (if *dst is null) or to *dst. + * If the result is a bitset_container_t and *dst is null, then there was a failure. + */ +bool array_array_container_inplace_union(array_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* + * Same as array_array_container_union except that it will more eagerly produce + * a bitset. + */ +bool array_array_container_lazy_union(const array_container_t *src_1, + const array_container_t *src_2, + void **dst); + +/* + * Same as array_array_container_inplace_union except that it will more eagerly produce + * a bitset. + */ +bool array_array_container_lazy_inplace_union(array_container_t *src_1, + const array_container_t *src_2, + void **dst); + +/* Compute the union of src_1 and src_2 and write the result to + * dst. We assume that dst is a + * valid container. The result might need to be further converted to array or + * bitset container, + * the caller is responsible for the eventual conversion. */ +void array_run_container_union(const array_container_t *src_1, + const run_container_t *src_2, + run_container_t *dst); + +/* Compute the union of src_1 and src_2 and write the result to + * src2. The result might need to be further converted to array or + * bitset container, + * the caller is responsible for the eventual conversion. */ +void array_run_container_inplace_union(const array_container_t *src_1, + run_container_t *src_2); + +/* Compute the union of src_1 and src_2 and write the result to + * dst. It is allowed for dst to be src_2. + * If run_container_is_full(src_1) is true, you must not be calling this + *function. + **/ +void run_bitset_container_union(const run_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* Compute the union of src_1 and src_2 and write the result to + * dst. It is allowed for dst to be src_2. This version does not + * update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). + * If run_container_is_full(src_1) is true, you must not be calling this + * function. + * */ +void run_bitset_container_lazy_union(const run_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +#endif /* INCLUDE_CONTAINERS_MIXED_UNION_H_ */ +/* end file include/roaring/containers/mixed_union.h */ +/* begin file include/roaring/containers/mixed_xor.h */ +/* + * mixed_xor.h + * + */ + +#ifndef INCLUDE_CONTAINERS_MIXED_XOR_H_ +#define INCLUDE_CONTAINERS_MIXED_XOR_H_ + +/* These functions appear to exclude cases where the + * inputs have the same type and the output is guaranteed + * to have the same type as the inputs. Eg, bitset unions + */ + +/* + * Java implementation (as of May 2016) for array_run, run_run + * and bitset_run don't do anything different for inplace. + * (They are not truly in place.) + */ + + + +/* Compute the xor of src_1 and src_2 and write the result to + * dst (which has no container initially). + * Result is true iff dst is a bitset */ +bool array_bitset_container_xor(const array_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +/* Compute the xor of src_1 and src_2 and write the result to + * dst. It is allowed for src_2 to be dst. This version does not + * update the cardinality of dst (it is set to BITSET_UNKNOWN_CARDINALITY). + */ + +void array_bitset_container_lazy_xor(const array_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); +/* Compute the xor of src_1 and src_2 and write the result to + * dst (which has no container initially). Return value is + * "dst is a bitset" + */ + +bool bitset_bitset_container_xor(const bitset_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +/* Compute the xor of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_xor(const run_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +/* lazy xor. Dst is initialized and may be equal to src_2. + * Result is left as a bitset container, even if actual + * cardinality would dictate an array container. + */ + +void run_bitset_container_lazy_xor(const run_container_t *src_1, + const bitset_container_t *src_2, + bitset_container_t *dst); + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int array_run_container_xor(const array_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* dst does not initially have a valid container. Creates either + * an array or a bitset container, indicated by return code + */ + +bool array_array_container_xor(const array_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* dst does not initially have a valid container. Creates either + * an array or a bitset container, indicated by return code. + * A bitset container will not have a valid cardinality and the + * container type might not be correct for the actual cardinality + */ + +bool array_array_container_lazy_xor(const array_container_t *src_1, + const array_container_t *src_2, void **dst); + +/* Dst is a valid run container. (Can it be src_2? Let's say not.) + * Leaves result as run container, even if other options are + * smaller. + */ + +void array_run_container_lazy_xor(const array_container_t *src_1, + const run_container_t *src_2, + run_container_t *dst); + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int run_run_container_xor(const run_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* INPLACE versions (initial implementation may not exploit all inplace + * opportunities (if any...) + */ + +/* Compute the xor of src_1 and src_2 and write the result to + * dst (which has no container initially). It will modify src_1 + * to be dst if the result is a bitset. Otherwise, it will + * free src_1 and dst will be a new array container. In both + * cases, the caller is responsible for deallocating dst. + * Returns true iff dst is a bitset */ + +bool bitset_array_container_ixor(bitset_container_t *src_1, + const array_container_t *src_2, void **dst); + +bool bitset_bitset_container_ixor(bitset_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +bool array_bitset_container_ixor(array_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +/* Compute the xor of src_1 and src_2 and write the result to + * dst. Result may be either a bitset or an array container + * (returns "result is bitset"). dst does not initially have + * any container, but becomes either a bitset container (return + * result true) or an array container. + */ + +bool run_bitset_container_ixor(run_container_t *src_1, + const bitset_container_t *src_2, void **dst); + +bool bitset_run_container_ixor(bitset_container_t *src_1, + const run_container_t *src_2, void **dst); + +/* dst does not indicate a valid container initially. Eventually it + * can become any kind of container. + */ + +int array_run_container_ixor(array_container_t *src_1, + const run_container_t *src_2, void **dst); + +int run_array_container_ixor(run_container_t *src_1, + const array_container_t *src_2, void **dst); + +bool array_array_container_ixor(array_container_t *src_1, + const array_container_t *src_2, void **dst); + +int run_run_container_ixor(run_container_t *src_1, const run_container_t *src_2, + void **dst); +#endif +/* end file include/roaring/containers/mixed_xor.h */ +/* begin file include/roaring/containers/containers.h */ +#ifndef CONTAINERS_CONTAINERS_H +#define CONTAINERS_CONTAINERS_H + +#include +#include +#include + + +// would enum be possible or better? + +/** + * The switch case statements follow + * BITSET_CONTAINER_TYPE_CODE -- ARRAY_CONTAINER_TYPE_CODE -- + * RUN_CONTAINER_TYPE_CODE + * so it makes more sense to number them 1, 2, 3 (in the vague hope that the + * compiler might exploit this ordering). + */ + +#define BITSET_CONTAINER_TYPE_CODE 1 +#define ARRAY_CONTAINER_TYPE_CODE 2 +#define RUN_CONTAINER_TYPE_CODE 3 +#define SHARED_CONTAINER_TYPE_CODE 4 + +// macro for pairing container type codes +#define CONTAINER_PAIR(c1, c2) (4 * (c1) + (c2)) + +/** + * A shared container is a wrapper around a container + * with reference counting. + */ + +struct shared_container_s { + void *container; + uint8_t typecode; + uint32_t counter; // to be managed atomically +}; + +typedef struct shared_container_s shared_container_t; + +/* + * With copy_on_write = true + * Create a new shared container if the typecode is not SHARED_CONTAINER_TYPE, + * otherwise, increase the count + * If copy_on_write = false, then clone. + * Return NULL in case of failure. + **/ +void *get_copy_of_container(void *container, uint8_t *typecode, + bool copy_on_write); + +/* Frees a shared container (actually decrement its counter and only frees when + * the counter falls to zero). */ +void shared_container_free(shared_container_t *container); + +/* extract a copy from the shared container, freeing the shared container if +there is just one instance left, +clone instances when the counter is higher than one +*/ +void *shared_container_extract_copy(shared_container_t *container, + uint8_t *typecode); + +/* access to container underneath */ +static inline const void *container_unwrap_shared( + const void *candidate_shared_container, uint8_t *type) { + if (*type == SHARED_CONTAINER_TYPE_CODE) { + *type = + ((const shared_container_t *)candidate_shared_container)->typecode; + assert(*type != SHARED_CONTAINER_TYPE_CODE); + return ((const shared_container_t *)candidate_shared_container)->container; + } else { + return candidate_shared_container; + } +} + + +/* access to container underneath */ +static inline void *container_mutable_unwrap_shared( + void *candidate_shared_container, uint8_t *type) { + if (*type == SHARED_CONTAINER_TYPE_CODE) { + *type = + ((shared_container_t *)candidate_shared_container)->typecode; + assert(*type != SHARED_CONTAINER_TYPE_CODE); + return ((shared_container_t *)candidate_shared_container)->container; + } else { + return candidate_shared_container; + } +} + +/* access to container underneath and queries its type */ +static inline uint8_t get_container_type(const void *container, uint8_t type) { + if (type == SHARED_CONTAINER_TYPE_CODE) { + return ((const shared_container_t *)container)->typecode; + } else { + return type; + } +} + +/** + * Copies a container, requires a typecode. This allocates new memory, caller + * is responsible for deallocation. If the container is not shared, then it is + * physically cloned. Shareable containers are not clonable. + */ +void *container_clone(const void *container, uint8_t typecode); + +/* access to container underneath, cloning it if needed */ +static inline void *get_writable_copy_if_shared( + void *candidate_shared_container, uint8_t *type) { + if (*type == SHARED_CONTAINER_TYPE_CODE) { + return shared_container_extract_copy( + (shared_container_t *)candidate_shared_container, type); + } else { + return candidate_shared_container; + } +} + +/** + * End of shared container code + */ + +static const char *container_names[] = {"bitset", "array", "run", "shared"}; +static const char *shared_container_names[] = { + "bitset (shared)", "array (shared)", "run (shared)"}; + +// no matter what the initial container was, convert it to a bitset +// if a new container is produced, caller responsible for freeing the previous +// one +// container should not be a shared container +static inline void *container_to_bitset(void *container, uint8_t typecode) { + bitset_container_t *result = NULL; + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return container; // nothing to do + case ARRAY_CONTAINER_TYPE_CODE: + result = + bitset_container_from_array((array_container_t *)container); + return result; + case RUN_CONTAINER_TYPE_CODE: + result = bitset_container_from_run((run_container_t *)container); + return result; + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +/** + * Get the container name from the typecode + */ +static inline const char *get_container_name(uint8_t typecode) { + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return container_names[0]; + case ARRAY_CONTAINER_TYPE_CODE: + return container_names[1]; + case RUN_CONTAINER_TYPE_CODE: + return container_names[2]; + case SHARED_CONTAINER_TYPE_CODE: + return container_names[3]; + default: + assert(false); + __builtin_unreachable(); + return "unknown"; + } +} + +static inline const char *get_full_container_name(const void *container, + uint8_t typecode) { + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return container_names[0]; + case ARRAY_CONTAINER_TYPE_CODE: + return container_names[1]; + case RUN_CONTAINER_TYPE_CODE: + return container_names[2]; + case SHARED_CONTAINER_TYPE_CODE: + switch (((const shared_container_t *)container)->typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return shared_container_names[0]; + case ARRAY_CONTAINER_TYPE_CODE: + return shared_container_names[1]; + case RUN_CONTAINER_TYPE_CODE: + return shared_container_names[2]; + default: + assert(false); + __builtin_unreachable(); + return "unknown"; + } + break; + default: + assert(false); + __builtin_unreachable(); + return "unknown"; + } + __builtin_unreachable(); + return NULL; +} + +/** + * Get the container cardinality (number of elements), requires a typecode + */ +static inline int container_get_cardinality(const void *container, + uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_cardinality( + (const bitset_container_t *)container); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_cardinality( + (const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_cardinality( + (const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + + + +// returns true if a container is known to be full. Note that a lazy bitset +// container +// might be full without us knowing +static inline bool container_is_full(const void *container, uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_cardinality( + (const bitset_container_t *)container) == (1 << 16); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_cardinality( + (const array_container_t *)container) == (1 << 16); + case RUN_CONTAINER_TYPE_CODE: + return run_container_is_full((const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +static inline int container_shrink_to_fit(void *container, uint8_t typecode) { + container = container_mutable_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return 0; // no shrinking possible + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_shrink_to_fit( + (array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_shrink_to_fit((run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + + +/** + * make a container with a run of ones + */ +/* initially always use a run container, even if an array might be + * marginally + * smaller */ +static inline void *container_range_of_ones(uint32_t range_start, + uint32_t range_end, + uint8_t *result_type) { + assert(range_end >= range_start); + uint64_t cardinality = range_end - range_start + 1; + if(cardinality <= 2) { + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return array_container_create_range(range_start, range_end); + } else { + *result_type = RUN_CONTAINER_TYPE_CODE; + return run_container_create_range(range_start, range_end); + } +} + + +/* Create a container with all the values between in [min,max) at a + distance k*step from min. */ +static inline void *container_from_range(uint8_t *type, uint32_t min, + uint32_t max, uint16_t step) { + if (step == 0) return NULL; // being paranoid + if (step == 1) { + return container_range_of_ones(min,max,type); + // Note: the result is not always a run (need to check the cardinality) + //*type = RUN_CONTAINER_TYPE_CODE; + //return run_container_create_range(min, max); + } + int size = (max - min + step - 1) / step; + if (size <= DEFAULT_MAX_SIZE) { // array container + *type = ARRAY_CONTAINER_TYPE_CODE; + array_container_t *array = array_container_create_given_capacity(size); + array_container_add_from_range(array, min, max, step); + assert(array->cardinality == size); + return array; + } else { // bitset container + *type = BITSET_CONTAINER_TYPE_CODE; + bitset_container_t *bitset = bitset_container_create(); + bitset_container_add_from_range(bitset, min, max, step); + assert(bitset->cardinality == size); + return bitset; + } +} + +/** + * "repair" the container after lazy operations. + */ +static inline void *container_repair_after_lazy(void *container, + uint8_t *typecode) { + container = get_writable_copy_if_shared( + container, typecode); // TODO: this introduces unnecessary cloning + void *result = NULL; + switch (*typecode) { + case BITSET_CONTAINER_TYPE_CODE: + ((bitset_container_t *)container)->cardinality = + bitset_container_compute_cardinality( + (bitset_container_t *)container); + if (((bitset_container_t *)container)->cardinality <= + DEFAULT_MAX_SIZE) { + result = array_container_from_bitset( + (const bitset_container_t *)container); + bitset_container_free((bitset_container_t *)container); + *typecode = ARRAY_CONTAINER_TYPE_CODE; + return result; + } + return container; + case ARRAY_CONTAINER_TYPE_CODE: + return container; // nothing to do + case RUN_CONTAINER_TYPE_CODE: + return convert_run_to_efficient_container_and_free( + (run_container_t *)container, typecode); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +/** + * Writes the underlying array to buf, outputs how many bytes were written. + * This is meant to be byte-by-byte compatible with the Java and Go versions of + * Roaring. + * The number of bytes written should be + * container_write(container, buf). + * + */ +static inline int32_t container_write(const void *container, uint8_t typecode, + char *buf) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_write((const bitset_container_t *)container, buf); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_write((const array_container_t *)container, buf); + case RUN_CONTAINER_TYPE_CODE: + return run_container_write((const run_container_t *)container, buf); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +/** + * Get the container size in bytes under portable serialization (see + * container_write), requires a + * typecode + */ +static inline int32_t container_size_in_bytes(const void *container, + uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_size_in_bytes( + (const bitset_container_t *)container); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_size_in_bytes( + (const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_size_in_bytes((const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +/** + * print the container (useful for debugging), requires a typecode + */ +void container_printf(const void *container, uint8_t typecode); + +/** + * print the content of the container as a comma-separated list of 32-bit values + * starting at base, requires a typecode + */ +void container_printf_as_uint32_array(const void *container, uint8_t typecode, + uint32_t base); + +/** + * Checks whether a container is not empty, requires a typecode + */ +static inline bool container_nonzero_cardinality(const void *container, + uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_const_nonzero_cardinality( + (const bitset_container_t *)container); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_nonzero_cardinality( + (const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_nonzero_cardinality( + (const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +/** + * Recover memory from a container, requires a typecode + */ +void container_free(void *container, uint8_t typecode); + +/** + * Convert a container to an array of values, requires a typecode as well as a + * "base" (most significant values) + * Returns number of ints added. + */ +static inline int container_to_uint32_array(uint32_t *output, + const void *container, + uint8_t typecode, uint32_t base) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_to_uint32_array( + output, (const bitset_container_t *)container, base); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_to_uint32_array( + output, (const array_container_t *)container, base); + case RUN_CONTAINER_TYPE_CODE: + return run_container_to_uint32_array( + output, (const run_container_t *)container, base); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return 0; // unreached + } +} + +/** + * Add a value to a container, requires a typecode, fills in new_typecode and + * return (possibly different) container. + * This function may allocate a new container, and caller is responsible for + * memory deallocation + */ +static inline void *container_add(void *container, uint16_t val, + uint8_t typecode, uint8_t *new_typecode) { + container = get_writable_copy_if_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + bitset_container_set((bitset_container_t *)container, val); + *new_typecode = BITSET_CONTAINER_TYPE_CODE; + return container; + case ARRAY_CONTAINER_TYPE_CODE: { + array_container_t *ac = (array_container_t *)container; + if (array_container_try_add(ac, val, DEFAULT_MAX_SIZE) != -1) { + *new_typecode = ARRAY_CONTAINER_TYPE_CODE; + return ac; + } else { + bitset_container_t* bitset = bitset_container_from_array(ac); + bitset_container_add(bitset, val); + *new_typecode = BITSET_CONTAINER_TYPE_CODE; + return bitset; + } + } break; + case RUN_CONTAINER_TYPE_CODE: + // per Java, no container type adjustments are done (revisit?) + run_container_add((run_container_t *)container, val); + *new_typecode = RUN_CONTAINER_TYPE_CODE; + return container; + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Remove a value from a container, requires a typecode, fills in new_typecode + * and + * return (possibly different) container. + * This function may allocate a new container, and caller is responsible for + * memory deallocation + */ +static inline void *container_remove(void *container, uint16_t val, + uint8_t typecode, uint8_t *new_typecode) { + container = get_writable_copy_if_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + if (bitset_container_remove((bitset_container_t *)container, val)) { + if (bitset_container_cardinality( + (bitset_container_t *)container) <= DEFAULT_MAX_SIZE) { + *new_typecode = ARRAY_CONTAINER_TYPE_CODE; + return array_container_from_bitset( + (bitset_container_t *)container); + } + } + *new_typecode = typecode; + return container; + case ARRAY_CONTAINER_TYPE_CODE: + *new_typecode = typecode; + array_container_remove((array_container_t *)container, val); + return container; + case RUN_CONTAINER_TYPE_CODE: + // per Java, no container type adjustments are done (revisit?) + run_container_remove((run_container_t *)container, val); + *new_typecode = RUN_CONTAINER_TYPE_CODE; + return container; + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Check whether a value is in a container, requires a typecode + */ +static inline bool container_contains(const void *container, uint16_t val, + uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_get((const bitset_container_t *)container, + val); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_contains( + (const array_container_t *)container, val); + case RUN_CONTAINER_TYPE_CODE: + return run_container_contains((const run_container_t *)container, + val); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +/** + * Check whether a range of values from range_start (included) to range_end (excluded) + * is in a container, requires a typecode + */ +static inline bool container_contains_range(const void *container, uint32_t range_start, + uint32_t range_end, uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_get_range((const bitset_container_t *)container, + range_start, range_end); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_contains_range((const array_container_t *)container, + range_start, range_end); + case RUN_CONTAINER_TYPE_CODE: + return run_container_contains_range((const run_container_t *)container, + range_start, range_end); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +int32_t container_serialize(const void *container, uint8_t typecode, + char *buf) WARN_UNUSED; + +uint32_t container_serialization_len(const void *container, uint8_t typecode); + +void *container_deserialize(uint8_t typecode, const char *buf, size_t buf_len); + +/** + * Returns true if the two containers have the same content. Note that + * two containers having different types can be "equal" in this sense. + */ +static inline bool container_equals(const void *c1, uint8_t type1, + const void *c2, uint8_t type2) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return bitset_container_equals((const bitset_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + return run_container_equals_bitset((const run_container_t *)c2, + (const bitset_container_t *)c1); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return run_container_equals_bitset((const run_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + // java would always return false? + return array_container_equal_bitset((const array_container_t *)c2, + (const bitset_container_t *)c1); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + // java would always return false? + return array_container_equal_bitset((const array_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return run_container_equals_array((const run_container_t *)c2, + (const array_container_t *)c1); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + return run_container_equals_array((const run_container_t *)c1, + (const array_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return array_container_equals((const array_container_t *)c1, + (const array_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return run_container_equals((const run_container_t *)c1, + (const run_container_t *)c2); + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +/** + * Returns true if the container c1 is a subset of the container c2. Note that + * c1 can be a subset of c2 even if they have a different type. + */ +static inline bool container_is_subset(const void *c1, uint8_t type1, + const void *c2, uint8_t type2) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return bitset_container_is_subset((const bitset_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + return bitset_container_is_subset_run((const bitset_container_t *)c1, + (const run_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return run_container_is_subset_bitset((const run_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return false; // by construction, size(c1) > size(c2) + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return array_container_is_subset_bitset((const array_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return array_container_is_subset_run((const array_container_t *)c1, + (const run_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + return run_container_is_subset_array((const run_container_t *)c1, + (const array_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return array_container_is_subset((const array_container_t *)c1, + (const array_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return run_container_is_subset((const run_container_t *)c1, + (const run_container_t *)c2); + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +// macro-izations possibilities for generic non-inplace binary-op dispatch + +/** + * Compute intersection between two containers, generate a new container (having + * type result_type), requires a typecode. This allocates new memory, caller + * is responsible for deallocation. + */ +static inline void *container_and(const void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = bitset_bitset_container_intersection( + (const bitset_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + result = array_container_create(); + array_container_intersection((const array_container_t *)c1, + (const array_container_t *)c2, + (array_container_t *)result); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + run_container_intersection((const run_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + return convert_run_to_efficient_container_and_free( + (run_container_t *)result, result_type); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + result = array_container_create(); + array_bitset_container_intersection((const array_container_t *)c2, + (const bitset_container_t *)c1, + (array_container_t *)result); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + array_bitset_container_intersection((const array_container_t *)c1, + (const bitset_container_t *)c2, + (array_container_t *)result); + return result; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + *result_type = run_bitset_container_intersection( + (const run_container_t *)c2, + (const bitset_container_t *)c1, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = run_bitset_container_intersection( + (const run_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + array_run_container_intersection((const array_container_t *)c1, + (const run_container_t *)c2, + (array_container_t *)result); + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + array_run_container_intersection((const array_container_t *)c2, + (const run_container_t *)c1, + (array_container_t *)result); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Compute the size of the intersection between two containers. + */ +static inline int container_and_cardinality(const void *c1, uint8_t type1, + const void *c2, uint8_t type2) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return bitset_container_and_justcard( + (const bitset_container_t *)c1, (const bitset_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return array_container_intersection_cardinality( + (const array_container_t *)c1, (const array_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return run_container_intersection_cardinality( + (const run_container_t *)c1, (const run_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return array_bitset_container_intersection_cardinality( + (const array_container_t *)c2, (const bitset_container_t *)c1); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return array_bitset_container_intersection_cardinality( + (const array_container_t *)c1, (const bitset_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + return run_bitset_container_intersection_cardinality( + (const run_container_t *)c2, (const bitset_container_t *)c1); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return run_bitset_container_intersection_cardinality( + (const run_container_t *)c1, (const bitset_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return array_run_container_intersection_cardinality( + (const array_container_t *)c1, (const run_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + return array_run_container_intersection_cardinality( + (const array_container_t *)c2, (const run_container_t *)c1); + default: + assert(false); + __builtin_unreachable(); + return 0; + } +} + +/** + * Check whether two containers intersect. + */ +static inline bool container_intersect(const void *c1, uint8_t type1, const void *c2, + uint8_t type2) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return bitset_container_intersect( + (const bitset_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return array_container_intersect((const array_container_t *)c1, + (const array_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return run_container_intersect((const run_container_t *)c1, + (const run_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + return array_bitset_container_intersect((const array_container_t *)c2, + (const bitset_container_t *)c1); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return array_bitset_container_intersect((const array_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + return run_bitset_container_intersect( + (const run_container_t *)c2, + (const bitset_container_t *)c1); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + return run_bitset_container_intersect( + (const run_container_t *)c1, + (const bitset_container_t *)c2); + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + return array_run_container_intersect((const array_container_t *)c1, + (const run_container_t *)c2); + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + return array_run_container_intersect((const array_container_t *)c2, + (const run_container_t *)c1); + default: + assert(false); + __builtin_unreachable(); + return 0; + } +} + +/** + * Compute intersection between two containers, with result in the first + container if possible. If the returned pointer is identical to c1, + then the container has been modified. If the returned pointer is different + from c1, then a new container has been created and the caller is responsible + for freeing it. + The type of the first container may change. Returns the modified + (and possibly new) container. +*/ +static inline void *container_iand(void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = get_writable_copy_if_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = + bitset_bitset_container_intersection_inplace( + (bitset_container_t *)c1, (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + array_container_intersection_inplace((array_container_t *)c1, + (const array_container_t *)c2); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return c1; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + run_container_intersection((const run_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + // as of January 2016, Java code used non-in-place intersection for + // two runcontainers + return convert_run_to_efficient_container_and_free( + (run_container_t *)result, result_type); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + // c1 is a bitmap so no inplace possible + result = array_container_create(); + array_bitset_container_intersection((const array_container_t *)c2, + (const bitset_container_t *)c1, + (array_container_t *)result); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + array_bitset_container_intersection( + (const array_container_t *)c1, (const bitset_container_t *)c2, + (array_container_t *)c1); // allowed + return c1; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + // will attempt in-place computation + *result_type = run_bitset_container_intersection( + (const run_container_t *)c2, + (const bitset_container_t *)c1, &c1) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return c1; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = run_bitset_container_intersection( + (const run_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + array_run_container_intersection((const array_container_t *)c1, + (const run_container_t *)c2, + (array_container_t *)result); + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; // never bitset + array_run_container_intersection((const array_container_t *)c2, + (const run_container_t *)c1, + (array_container_t *)result); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Compute union between two containers, generate a new container (having type + * result_type), requires a typecode. This allocates new memory, caller + * is responsible for deallocation. + */ +static inline void *container_or(const void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + bitset_container_or((const bitset_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_union( + (const array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + run_container_union((const run_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + *result_type = RUN_CONTAINER_TYPE_CODE; + // todo: could be optimized since will never convert to array + result = convert_run_to_efficient_container_and_free( + (run_container_t *)result, (uint8_t *)result_type); + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + array_bitset_container_union((const array_container_t *)c2, + (const bitset_container_t *)c1, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + array_bitset_container_union((const array_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = run_container_create(); + *result_type = RUN_CONTAINER_TYPE_CODE; + run_container_copy((const run_container_t *)c2, + (run_container_t *)result); + return result; + } + result = bitset_container_create(); + run_bitset_container_union((const run_container_t *)c2, + (const bitset_container_t *)c1, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c1)) { + result = run_container_create(); + *result_type = RUN_CONTAINER_TYPE_CODE; + run_container_copy((const run_container_t *)c1, + (run_container_t *)result); + return result; + } + result = bitset_container_create(); + run_bitset_container_union((const run_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_union((const array_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + result = convert_run_to_efficient_container_and_free( + (run_container_t *)result, (uint8_t *)result_type); + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_union((const array_container_t *)c2, + (const run_container_t *)c1, + (run_container_t *)result); + result = convert_run_to_efficient_container_and_free( + (run_container_t *)result, (uint8_t *)result_type); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; // unreached + } +} + +/** + * Compute union between two containers, generate a new container (having type + * result_type), requires a typecode. This allocates new memory, caller + * is responsible for deallocation. + * + * This lazy version delays some operations such as the maintenance of the + * cardinality. It requires repair later on the generated containers. + */ +static inline void *container_lazy_or(const void *c1, uint8_t type1, + const void *c2, uint8_t type2, + uint8_t *result_type) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + bitset_container_or_nocard( + (const bitset_container_t *)c1, (const bitset_container_t *)c2, + (bitset_container_t *)result); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_lazy_union( + (const array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + run_container_union((const run_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + *result_type = RUN_CONTAINER_TYPE_CODE; + // we are being lazy + result = convert_run_to_efficient_container( + (run_container_t *)result, result_type); + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + array_bitset_container_lazy_union( + (const array_container_t *)c2, (const bitset_container_t *)c1, + (bitset_container_t *)result); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + array_bitset_container_lazy_union( + (const array_container_t *)c1, (const bitset_container_t *)c2, + (bitset_container_t *)result); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = run_container_create(); + *result_type = RUN_CONTAINER_TYPE_CODE; + run_container_copy((const run_container_t *)c2, + (run_container_t *)result); + return result; + } + result = bitset_container_create(); + run_bitset_container_lazy_union( + (const run_container_t *)c2, (const bitset_container_t *)c1, + (bitset_container_t *)result); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c1)) { + result = run_container_create(); + *result_type = RUN_CONTAINER_TYPE_CODE; + run_container_copy((const run_container_t *)c1, + (run_container_t *)result); + return result; + } + result = bitset_container_create(); + run_bitset_container_lazy_union( + (const run_container_t *)c1, (const bitset_container_t *)c2, + (bitset_container_t *)result); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_union((const array_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + *result_type = RUN_CONTAINER_TYPE_CODE; + // next line skipped since we are lazy + // result = convert_run_to_efficient_container(result, result_type); + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_union( + (const array_container_t *)c2, (const run_container_t *)c1, + (run_container_t *)result); // TODO make lazy + *result_type = RUN_CONTAINER_TYPE_CODE; + // next line skipped since we are lazy + // result = convert_run_to_efficient_container(result, result_type); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; // unreached + } +} + +/** + * Compute the union between two containers, with result in the first container. + * If the returned pointer is identical to c1, then the container has been + * modified. + * If the returned pointer is different from c1, then a new container has been + * created and the caller is responsible for freeing it. + * The type of the first container may change. Returns the modified + * (and possibly new) container +*/ +static inline void *container_ior(void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = get_writable_copy_if_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + bitset_container_or((const bitset_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)c1); +#ifdef OR_BITSET_CONVERSION_TO_FULL + if (((bitset_container_t *)c1)->cardinality == + (1 << 16)) { // we convert + result = run_container_create_range(0, (1 << 16)); + *result_type = RUN_CONTAINER_TYPE_CODE; + return result; + } +#endif + *result_type = BITSET_CONTAINER_TYPE_CODE; + return c1; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_inplace_union( + (array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + if((result == NULL) + && (*result_type == ARRAY_CONTAINER_TYPE_CODE)) { + return c1; // the computation was done in-place! + } + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + run_container_union_inplace((run_container_t *)c1, + (const run_container_t *)c2); + return convert_run_to_efficient_container((run_container_t *)c1, + result_type); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + array_bitset_container_union((const array_container_t *)c2, + (const bitset_container_t *)c1, + (bitset_container_t *)c1); + *result_type = BITSET_CONTAINER_TYPE_CODE; // never array + return c1; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + // c1 is an array, so no in-place possible + result = bitset_container_create(); + *result_type = BITSET_CONTAINER_TYPE_CODE; + array_bitset_container_union((const array_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = run_container_create(); + *result_type = RUN_CONTAINER_TYPE_CODE; + run_container_copy((const run_container_t *)c2, + (run_container_t *)result); + return result; + } + run_bitset_container_union((const run_container_t *)c2, + (const bitset_container_t *)c1, + (bitset_container_t *)c1); // allowed + *result_type = BITSET_CONTAINER_TYPE_CODE; + return c1; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c1)) { + *result_type = RUN_CONTAINER_TYPE_CODE; + + return c1; + } + result = bitset_container_create(); + run_bitset_container_union((const run_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_union((const array_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + result = convert_run_to_efficient_container_and_free( + (run_container_t *)result, result_type); + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + array_run_container_inplace_union((const array_container_t *)c2, + (run_container_t *)c1); + c1 = convert_run_to_efficient_container((run_container_t *)c1, + result_type); + return c1; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Compute the union between two containers, with result in the first container. + * If the returned pointer is identical to c1, then the container has been + * modified. + * If the returned pointer is different from c1, then a new container has been + * created and the caller is responsible for freeing it. + * The type of the first container may change. Returns the modified + * (and possibly new) container + * + * This lazy version delays some operations such as the maintenance of the + * cardinality. It requires repair later on the generated containers. +*/ +static inline void *container_lazy_ior(void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + assert(type1 != SHARED_CONTAINER_TYPE_CODE); + // c1 = get_writable_copy_if_shared(c1,&type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): +#ifdef LAZY_OR_BITSET_CONVERSION_TO_FULL + // if we have two bitsets, we might as well compute the cardinality + bitset_container_or((const bitset_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)c1); + // it is possible that two bitsets can lead to a full container + if (((bitset_container_t *)c1)->cardinality == + (1 << 16)) { // we convert + result = run_container_create_range(0, (1 << 16)); + *result_type = RUN_CONTAINER_TYPE_CODE; + return result; + } +#else + bitset_container_or_nocard((const bitset_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)c1); + +#endif + *result_type = BITSET_CONTAINER_TYPE_CODE; + return c1; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_lazy_inplace_union( + (array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + if((result == NULL) + && (*result_type == ARRAY_CONTAINER_TYPE_CODE)) { + return c1; // the computation was done in-place! + } + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + run_container_union_inplace((run_container_t *)c1, + (const run_container_t *)c2); + *result_type = RUN_CONTAINER_TYPE_CODE; + return convert_run_to_efficient_container((run_container_t *)c1, + result_type); + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + array_bitset_container_lazy_union( + (const array_container_t *)c2, (const bitset_container_t *)c1, + (bitset_container_t *)c1); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; // never array + return c1; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + // c1 is an array, so no in-place possible + result = bitset_container_create(); + *result_type = BITSET_CONTAINER_TYPE_CODE; + array_bitset_container_lazy_union( + (const array_container_t *)c1, (const bitset_container_t *)c2, + (bitset_container_t *)result); // is lazy + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = run_container_create(); + *result_type = RUN_CONTAINER_TYPE_CODE; + run_container_copy((const run_container_t *)c2, + (run_container_t *)result); + return result; + } + run_bitset_container_lazy_union( + (const run_container_t *)c2, (const bitset_container_t *)c1, + (bitset_container_t *)c1); // allowed // lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return c1; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c1)) { + *result_type = RUN_CONTAINER_TYPE_CODE; + return c1; + } + result = bitset_container_create(); + run_bitset_container_lazy_union( + (const run_container_t *)c1, (const bitset_container_t *)c2, + (bitset_container_t *)result); // lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_union((const array_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + *result_type = RUN_CONTAINER_TYPE_CODE; + // next line skipped since we are lazy + // result = convert_run_to_efficient_container_and_free(result, + // result_type); + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + array_run_container_inplace_union((const array_container_t *)c2, + (run_container_t *)c1); + *result_type = RUN_CONTAINER_TYPE_CODE; + // next line skipped since we are lazy + // result = convert_run_to_efficient_container_and_free(result, + // result_type); + return c1; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Compute symmetric difference (xor) between two containers, generate a new + * container (having type result_type), requires a typecode. This allocates new + * memory, caller is responsible for deallocation. + */ +static inline void *container_xor(const void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = bitset_bitset_container_xor( + (const bitset_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_xor( + (const array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + *result_type = + run_run_container_xor((const run_container_t *)c1, + (const run_container_t *)c2, &result); + return result; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_bitset_container_xor( + (const array_container_t *)c2, + (const bitset_container_t *)c1, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = array_bitset_container_xor( + (const array_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + *result_type = run_bitset_container_xor( + (const run_container_t *)c2, + (const bitset_container_t *)c1, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + + *result_type = run_bitset_container_xor( + (const run_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + *result_type = + array_run_container_xor((const array_container_t *)c1, + (const run_container_t *)c2, &result); + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + *result_type = + array_run_container_xor((const array_container_t *)c2, + (const run_container_t *)c1, &result); + return result; + + default: + assert(false); + __builtin_unreachable(); + return NULL; // unreached + } +} + +/** + * Compute xor between two containers, generate a new container (having type + * result_type), requires a typecode. This allocates new memory, caller + * is responsible for deallocation. + * + * This lazy version delays some operations such as the maintenance of the + * cardinality. It requires repair later on the generated containers. + */ +static inline void *container_lazy_xor(const void *c1, uint8_t type1, + const void *c2, uint8_t type2, + uint8_t *result_type) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + bitset_container_xor_nocard( + (const bitset_container_t *)c1, (const bitset_container_t *)c2, + (bitset_container_t *)result); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_lazy_xor( + (const array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + // nothing special done yet. + *result_type = + run_run_container_xor((const run_container_t *)c1, + (const run_container_t *)c2, &result); + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + *result_type = BITSET_CONTAINER_TYPE_CODE; + array_bitset_container_lazy_xor((const array_container_t *)c2, + (const bitset_container_t *)c1, + (bitset_container_t *)result); + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + *result_type = BITSET_CONTAINER_TYPE_CODE; + array_bitset_container_lazy_xor((const array_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + run_bitset_container_lazy_xor((const run_container_t *)c2, + (const bitset_container_t *)c1, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = bitset_container_create(); + run_bitset_container_lazy_xor((const run_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)result); + *result_type = BITSET_CONTAINER_TYPE_CODE; + return result; + + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_lazy_xor((const array_container_t *)c1, + (const run_container_t *)c2, + (run_container_t *)result); + *result_type = RUN_CONTAINER_TYPE_CODE; + // next line skipped since we are lazy + // result = convert_run_to_efficient_container(result, result_type); + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + result = run_container_create(); + array_run_container_lazy_xor((const array_container_t *)c2, + (const run_container_t *)c1, + (run_container_t *)result); + *result_type = RUN_CONTAINER_TYPE_CODE; + // next line skipped since we are lazy + // result = convert_run_to_efficient_container(result, result_type); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; // unreached + } +} + +/** + * Compute the xor between two containers, with result in the first container. + * If the returned pointer is identical to c1, then the container has been + * modified. + * If the returned pointer is different from c1, then a new container has been + * created and the caller is responsible for freeing it. + * The type of the first container may change. Returns the modified + * (and possibly new) container +*/ +static inline void *container_ixor(void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = get_writable_copy_if_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = bitset_bitset_container_ixor( + (bitset_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = array_array_container_ixor( + (array_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + *result_type = run_run_container_ixor( + (run_container_t *)c1, (const run_container_t *)c2, &result); + return result; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = bitset_array_container_ixor( + (bitset_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = array_bitset_container_ixor( + (array_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + + return result; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + *result_type = + bitset_run_container_ixor((bitset_container_t *)c1, + (const run_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = run_bitset_container_ixor( + (run_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + + return result; + + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + *result_type = array_run_container_ixor( + (array_container_t *)c1, (const run_container_t *)c2, &result); + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + *result_type = run_array_container_ixor( + (run_container_t *)c1, (const array_container_t *)c2, &result); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Compute the xor between two containers, with result in the first container. + * If the returned pointer is identical to c1, then the container has been + * modified. + * If the returned pointer is different from c1, then a new container has been + * created and the caller is responsible for freeing it. + * The type of the first container may change. Returns the modified + * (and possibly new) container + * + * This lazy version delays some operations such as the maintenance of the + * cardinality. It requires repair later on the generated containers. +*/ +static inline void *container_lazy_ixor(void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + assert(type1 != SHARED_CONTAINER_TYPE_CODE); + // c1 = get_writable_copy_if_shared(c1,&type1); + c2 = container_unwrap_shared(c2, &type2); + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + bitset_container_xor_nocard((bitset_container_t *)c1, + (const bitset_container_t *)c2, + (bitset_container_t *)c1); // is lazy + *result_type = BITSET_CONTAINER_TYPE_CODE; + return c1; + // TODO: other cases being lazy, esp. when we know inplace not likely + // could see the corresponding code for union + default: + // we may have a dirty bitset (without a precomputed cardinality) and + // calling container_ixor on it might be unsafe. + if( (type1 == BITSET_CONTAINER_TYPE_CODE) + && (((const bitset_container_t *)c1)->cardinality == BITSET_UNKNOWN_CARDINALITY)) { + ((bitset_container_t *)c1)->cardinality = bitset_container_compute_cardinality((bitset_container_t *)c1); + } + return container_ixor(c1, type1, c2, type2, result_type); + } +} + +/** + * Compute difference (andnot) between two containers, generate a new + * container (having type result_type), requires a typecode. This allocates new + * memory, caller is responsible for deallocation. + */ +static inline void *container_andnot(const void *c1, uint8_t type1, + const void *c2, uint8_t type2, + uint8_t *result_type) { + c1 = container_unwrap_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = bitset_bitset_container_andnot( + (const bitset_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + result = array_container_create(); + array_array_container_andnot((const array_container_t *)c1, + (const array_container_t *)c2, + (array_container_t *)result); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return result; + } + *result_type = + run_run_container_andnot((const run_container_t *)c1, + (const run_container_t *)c2, &result); + return result; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = bitset_array_container_andnot( + (const bitset_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + result = array_container_create(); + array_bitset_container_andnot((const array_container_t *)c1, + (const bitset_container_t *)c2, + (array_container_t *)result); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return result; + } + *result_type = bitset_run_container_andnot( + (const bitset_container_t *)c1, + (const run_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + + *result_type = run_bitset_container_andnot( + (const run_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + if (run_container_is_full((const run_container_t *)c2)) { + result = array_container_create(); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return result; + } + result = array_container_create(); + array_run_container_andnot((const array_container_t *)c1, + (const run_container_t *)c2, + (array_container_t *)result); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + *result_type = run_array_container_andnot( + (const run_container_t *)c1, (const array_container_t *)c2, + &result); + return result; + + default: + assert(false); + __builtin_unreachable(); + return NULL; // unreached + } +} + +/** + * Compute the andnot between two containers, with result in the first + * container. + * If the returned pointer is identical to c1, then the container has been + * modified. + * If the returned pointer is different from c1, then a new container has been + * created and the caller is responsible for freeing it. + * The type of the first container may change. Returns the modified + * (and possibly new) container +*/ +static inline void *container_iandnot(void *c1, uint8_t type1, const void *c2, + uint8_t type2, uint8_t *result_type) { + c1 = get_writable_copy_if_shared(c1, &type1); + c2 = container_unwrap_shared(c2, &type2); + void *result = NULL; + switch (CONTAINER_PAIR(type1, type2)) { + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = bitset_bitset_container_iandnot( + (bitset_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + array_array_container_iandnot((array_container_t *)c1, + (const array_container_t *)c2); + *result_type = ARRAY_CONTAINER_TYPE_CODE; + return c1; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + *result_type = run_run_container_iandnot( + (run_container_t *)c1, (const run_container_t *)c2, &result); + return result; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + ARRAY_CONTAINER_TYPE_CODE): + *result_type = bitset_array_container_iandnot( + (bitset_container_t *)c1, + (const array_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = ARRAY_CONTAINER_TYPE_CODE; + + array_bitset_container_iandnot((array_container_t *)c1, + (const bitset_container_t *)c2); + return c1; + + case CONTAINER_PAIR(BITSET_CONTAINER_TYPE_CODE, + RUN_CONTAINER_TYPE_CODE): + *result_type = bitset_run_container_iandnot( + (bitset_container_t *)c1, + (const run_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + + return result; + + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, + BITSET_CONTAINER_TYPE_CODE): + *result_type = run_bitset_container_iandnot( + (run_container_t *)c1, + (const bitset_container_t *)c2, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + + return result; + + case CONTAINER_PAIR(ARRAY_CONTAINER_TYPE_CODE, RUN_CONTAINER_TYPE_CODE): + *result_type = ARRAY_CONTAINER_TYPE_CODE; + array_run_container_iandnot((array_container_t *)c1, + (const run_container_t *)c2); + return c1; + case CONTAINER_PAIR(RUN_CONTAINER_TYPE_CODE, ARRAY_CONTAINER_TYPE_CODE): + *result_type = run_array_container_iandnot( + (run_container_t *)c1, (const array_container_t *)c2, &result); + return result; + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * Visit all values x of the container once, passing (base+x,ptr) + * to iterator. You need to specify a container and its type. + * Returns true if the iteration should continue. + */ +static inline bool container_iterate(const void *container, uint8_t typecode, + uint32_t base, roaring_iterator iterator, + void *ptr) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_iterate( + (const bitset_container_t *)container, base, iterator, ptr); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_iterate((const array_container_t *)container, + base, iterator, ptr); + case RUN_CONTAINER_TYPE_CODE: + return run_container_iterate((const run_container_t *)container, + base, iterator, ptr); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +static inline bool container_iterate64(const void *container, uint8_t typecode, + uint32_t base, + roaring_iterator64 iterator, + uint64_t high_bits, void *ptr) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_iterate64( + (const bitset_container_t *)container, base, iterator, + high_bits, ptr); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_iterate64( + (const array_container_t *)container, base, iterator, high_bits, + ptr); + case RUN_CONTAINER_TYPE_CODE: + return run_container_iterate64((const run_container_t *)container, + base, iterator, high_bits, ptr); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +static inline void *container_not(const void *c, uint8_t typ, + uint8_t *result_type) { + c = container_unwrap_shared(c, &typ); + void *result = NULL; + switch (typ) { + case BITSET_CONTAINER_TYPE_CODE: + *result_type = bitset_container_negation( + (const bitset_container_t *)c, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case ARRAY_CONTAINER_TYPE_CODE: + result = bitset_container_create(); + *result_type = BITSET_CONTAINER_TYPE_CODE; + array_container_negation((const array_container_t *)c, + (bitset_container_t *)result); + return result; + case RUN_CONTAINER_TYPE_CODE: + *result_type = + run_container_negation((const run_container_t *)c, &result); + return result; + + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +static inline void *container_not_range(const void *c, uint8_t typ, + uint32_t range_start, + uint32_t range_end, + uint8_t *result_type) { + c = container_unwrap_shared(c, &typ); + void *result = NULL; + switch (typ) { + case BITSET_CONTAINER_TYPE_CODE: + *result_type = + bitset_container_negation_range((const bitset_container_t *)c, + range_start, range_end, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case ARRAY_CONTAINER_TYPE_CODE: + *result_type = + array_container_negation_range((const array_container_t *)c, + range_start, range_end, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case RUN_CONTAINER_TYPE_CODE: + *result_type = run_container_negation_range( + (const run_container_t *)c, range_start, range_end, &result); + return result; + + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +static inline void *container_inot(void *c, uint8_t typ, uint8_t *result_type) { + c = get_writable_copy_if_shared(c, &typ); + void *result = NULL; + switch (typ) { + case BITSET_CONTAINER_TYPE_CODE: + *result_type = bitset_container_negation_inplace( + (bitset_container_t *)c, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case ARRAY_CONTAINER_TYPE_CODE: + // will never be inplace + result = bitset_container_create(); + *result_type = BITSET_CONTAINER_TYPE_CODE; + array_container_negation((array_container_t *)c, + (bitset_container_t *)result); + array_container_free((array_container_t *)c); + return result; + case RUN_CONTAINER_TYPE_CODE: + *result_type = + run_container_negation_inplace((run_container_t *)c, &result); + return result; + + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +static inline void *container_inot_range(void *c, uint8_t typ, + uint32_t range_start, + uint32_t range_end, + uint8_t *result_type) { + c = get_writable_copy_if_shared(c, &typ); + void *result = NULL; + switch (typ) { + case BITSET_CONTAINER_TYPE_CODE: + *result_type = + bitset_container_negation_range_inplace( + (bitset_container_t *)c, range_start, range_end, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case ARRAY_CONTAINER_TYPE_CODE: + *result_type = + array_container_negation_range_inplace( + (array_container_t *)c, range_start, range_end, &result) + ? BITSET_CONTAINER_TYPE_CODE + : ARRAY_CONTAINER_TYPE_CODE; + return result; + case RUN_CONTAINER_TYPE_CODE: + *result_type = run_container_negation_range_inplace( + (run_container_t *)c, range_start, range_end, &result); + return result; + + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return NULL; + } +} + +/** + * If the element of given rank is in this container, supposing that + * the first + * element has rank start_rank, then the function returns true and + * sets element + * accordingly. + * Otherwise, it returns false and update start_rank. + */ +static inline bool container_select(const void *container, uint8_t typecode, + uint32_t *start_rank, uint32_t rank, + uint32_t *element) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_select((const bitset_container_t *)container, + start_rank, rank, element); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_select((const array_container_t *)container, + start_rank, rank, element); + case RUN_CONTAINER_TYPE_CODE: + return run_container_select((const run_container_t *)container, + start_rank, rank, element); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +static inline uint16_t container_maximum(const void *container, + uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_maximum((const bitset_container_t *)container); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_maximum((const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_maximum((const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +static inline uint16_t container_minimum(const void *container, + uint8_t typecode) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_minimum((const bitset_container_t *)container); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_minimum((const array_container_t *)container); + case RUN_CONTAINER_TYPE_CODE: + return run_container_minimum((const run_container_t *)container); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +// number of values smaller or equal to x +static inline int container_rank(const void *container, uint8_t typecode, + uint16_t x) { + container = container_unwrap_shared(container, &typecode); + switch (typecode) { + case BITSET_CONTAINER_TYPE_CODE: + return bitset_container_rank((const bitset_container_t *)container, x); + case ARRAY_CONTAINER_TYPE_CODE: + return array_container_rank((const array_container_t *)container, x); + case RUN_CONTAINER_TYPE_CODE: + return run_container_rank((const run_container_t *)container, x); + case SHARED_CONTAINER_TYPE_CODE: + default: + assert(false); + __builtin_unreachable(); + return false; + } +} + +/** + * Add all values in range [min, max] to a given container. + * + * If the returned pointer is different from $container, then a new container + * has been created and the caller is responsible for freeing it. + * The type of the first container may change. Returns the modified + * (and possibly new) container. + */ +static inline void *container_add_range(void *container, uint8_t type, + uint32_t min, uint32_t max, + uint8_t *result_type) { + // NB: when selecting new container type, we perform only inexpensive checks + switch (type) { + case BITSET_CONTAINER_TYPE_CODE: { + bitset_container_t *bitset = (bitset_container_t *) container; + + int32_t union_cardinality = 0; + union_cardinality += bitset->cardinality; + union_cardinality += max - min + 1; + union_cardinality -= bitset_lenrange_cardinality(bitset->array, min, max-min); + + if (union_cardinality == INT32_C(0x10000)) { + *result_type = RUN_CONTAINER_TYPE_CODE; + return run_container_create_range(0, INT32_C(0x10000)); + } else { + *result_type = BITSET_CONTAINER_TYPE_CODE; + bitset_set_lenrange(bitset->array, min, max - min); + bitset->cardinality = union_cardinality; + return bitset; + } + } + case ARRAY_CONTAINER_TYPE_CODE: { + array_container_t *array = (array_container_t *) container; + + int32_t nvals_greater = count_greater(array->array, array->cardinality, max); + int32_t nvals_less = count_less(array->array, array->cardinality - nvals_greater, min); + int32_t union_cardinality = nvals_less + (max - min + 1) + nvals_greater; + + if (union_cardinality == INT32_C(0x10000)) { + *result_type = RUN_CONTAINER_TYPE_CODE; + return run_container_create_range(0, INT32_C(0x10000)); + } else if (union_cardinality <= DEFAULT_MAX_SIZE) { + *result_type = ARRAY_CONTAINER_TYPE_CODE; + array_container_add_range_nvals(array, min, max, nvals_less, nvals_greater); + return array; + } else { + *result_type = BITSET_CONTAINER_TYPE_CODE; + bitset_container_t *bitset = bitset_container_from_array(array); + bitset_set_lenrange(bitset->array, min, max - min); + bitset->cardinality = union_cardinality; + return bitset; + } + } + case RUN_CONTAINER_TYPE_CODE: { + run_container_t *run = (run_container_t *) container; + + int32_t nruns_greater = rle16_count_greater(run->runs, run->n_runs, max); + int32_t nruns_less = rle16_count_less(run->runs, run->n_runs - nruns_greater, min); + + int32_t run_size_bytes = (nruns_less + 1 + nruns_greater) * sizeof(rle16_t); + int32_t bitset_size_bytes = BITSET_CONTAINER_SIZE_IN_WORDS * sizeof(uint64_t); + + if (run_size_bytes <= bitset_size_bytes) { + run_container_add_range_nruns(run, min, max, nruns_less, nruns_greater); + *result_type = RUN_CONTAINER_TYPE_CODE; + return run; + } else { + *result_type = BITSET_CONTAINER_TYPE_CODE; + return bitset_container_from_run_range(run, min, max); + } + } + case SHARED_CONTAINER_TYPE_CODE: + default: + __builtin_unreachable(); + } +} + +/* + * Removes all elements in range [min, max]. + * Returns one of: + * - NULL if no elements left + * - pointer to the original container + * - pointer to a newly-allocated container (if it is more efficient) + * + * If the returned pointer is different from $container, then a new container + * has been created and the caller is responsible for freeing the original container. + */ +static inline void *container_remove_range(void *container, uint8_t type, + uint32_t min, uint32_t max, + uint8_t *result_type) { + switch (type) { + case BITSET_CONTAINER_TYPE_CODE: { + bitset_container_t *bitset = (bitset_container_t *) container; + + int32_t result_cardinality = bitset->cardinality - + bitset_lenrange_cardinality(bitset->array, min, max-min); + + if (result_cardinality == 0) { + return NULL; + } else if (result_cardinality < DEFAULT_MAX_SIZE) { + *result_type = ARRAY_CONTAINER_TYPE_CODE; + bitset_reset_range(bitset->array, min, max+1); + bitset->cardinality = result_cardinality; + return array_container_from_bitset(bitset); + } else { + *result_type = BITSET_CONTAINER_TYPE_CODE; + bitset_reset_range(bitset->array, min, max+1); + bitset->cardinality = result_cardinality; + return bitset; + } + } + case ARRAY_CONTAINER_TYPE_CODE: { + array_container_t *array = (array_container_t *) container; + + int32_t nvals_greater = count_greater(array->array, array->cardinality, max); + int32_t nvals_less = count_less(array->array, array->cardinality - nvals_greater, min); + int32_t result_cardinality = nvals_less + nvals_greater; + + if (result_cardinality == 0) { + return NULL; + } else { + *result_type = ARRAY_CONTAINER_TYPE_CODE; + array_container_remove_range(array, nvals_less, + array->cardinality - result_cardinality); + return array; + } + } + case RUN_CONTAINER_TYPE_CODE: { + run_container_t *run = (run_container_t *) container; + + if (run->n_runs == 0) { + return NULL; + } + if (min <= run_container_minimum(run) && max >= run_container_maximum(run)) { + return NULL; + } + + run_container_remove_range(run, min, max); + + if (run_container_serialized_size_in_bytes(run->n_runs) <= + bitset_container_serialized_size_in_bytes()) { + *result_type = RUN_CONTAINER_TYPE_CODE; + return run; + } else { + *result_type = BITSET_CONTAINER_TYPE_CODE; + return bitset_container_from_run(run); + } + } + case SHARED_CONTAINER_TYPE_CODE: + default: + __builtin_unreachable(); + } +} + +#endif +/* end file include/roaring/containers/containers.h */ +/* begin file include/roaring/roaring_array.h */ +#ifndef INCLUDE_ROARING_ARRAY_H +#define INCLUDE_ROARING_ARRAY_H +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#define MAX_CONTAINERS 65536 + +#define SERIALIZATION_ARRAY_UINT32 1 +#define SERIALIZATION_CONTAINER 2 + +#define ROARING_FLAG_COW UINT8_C(0x1) +#define ROARING_FLAG_FROZEN UINT8_C(0x2) + +enum { + SERIAL_COOKIE_NO_RUNCONTAINER = 12346, + SERIAL_COOKIE = 12347, + FROZEN_COOKIE = 13766, + NO_OFFSET_THRESHOLD = 4 +}; + +/** + * Roaring arrays are array-based key-value pairs having containers as values + * and 16-bit integer keys. A roaring bitmap might be implemented as such. + */ + +// parallel arrays. Element sizes quite different. +// Alternative is array +// of structs. Which would have better +// cache performance through binary searches? + +typedef struct roaring_array_s { + int32_t size; + int32_t allocation_size; + void **containers; + uint16_t *keys; + uint8_t *typecodes; + uint8_t flags; +} roaring_array_t; + +/** + * Create a new roaring array + */ +roaring_array_t *ra_create(void); + +/** + * Initialize an existing roaring array with the specified capacity (in number + * of containers) + */ +bool ra_init_with_capacity(roaring_array_t *new_ra, uint32_t cap); + +/** + * Initialize with zero capacity + */ +void ra_init(roaring_array_t *t); + +/** + * Copies this roaring array, we assume that dest is not initialized + */ +bool ra_copy(const roaring_array_t *source, roaring_array_t *dest, + bool copy_on_write); + +/* + * Shrinks the capacity, returns the number of bytes saved. + */ +int ra_shrink_to_fit(roaring_array_t *ra); + +/** + * Copies this roaring array, we assume that dest is initialized + */ +bool ra_overwrite(const roaring_array_t *source, roaring_array_t *dest, + bool copy_on_write); + +/** + * Frees the memory used by a roaring array + */ +void ra_clear(roaring_array_t *r); + +/** + * Frees the memory used by a roaring array, but does not free the containers + */ +void ra_clear_without_containers(roaring_array_t *r); + +/** + * Frees just the containers + */ +void ra_clear_containers(roaring_array_t *ra); + +/** + * Get the index corresponding to a 16-bit key + */ +static inline int32_t ra_get_index(const roaring_array_t *ra, uint16_t x) { + if ((ra->size == 0) || ra->keys[ra->size - 1] == x) return ra->size - 1; + return binarySearch(ra->keys, (int32_t)ra->size, x); +} + +/** + * Retrieves the container at index i, filling in the typecode + */ +static inline void *ra_get_container_at_index(const roaring_array_t *ra, uint16_t i, + uint8_t *typecode) { + *typecode = ra->typecodes[i]; + return ra->containers[i]; +} + +/** + * Retrieves the key at index i + */ +uint16_t ra_get_key_at_index(const roaring_array_t *ra, uint16_t i); + +/** + * Add a new key-value pair at index i + */ +void ra_insert_new_key_value_at(roaring_array_t *ra, int32_t i, uint16_t key, + void *container, uint8_t typecode); + +/** + * Append a new key-value pair + */ +void ra_append(roaring_array_t *ra, uint16_t s, void *c, uint8_t typecode); + +/** + * Append a new key-value pair to ra, cloning (in COW sense) a value from sa + * at index index + */ +void ra_append_copy(roaring_array_t *ra, const roaring_array_t *sa, + uint16_t index, bool copy_on_write); + +/** + * Append new key-value pairs to ra, cloning (in COW sense) values from sa + * at indexes + * [start_index, end_index) + */ +void ra_append_copy_range(roaring_array_t *ra, const roaring_array_t *sa, + int32_t start_index, int32_t end_index, + bool copy_on_write); + +/** appends from sa to ra, ending with the greatest key that is + * is less or equal stopping_key + */ +void ra_append_copies_until(roaring_array_t *ra, const roaring_array_t *sa, + uint16_t stopping_key, bool copy_on_write); + +/** appends from sa to ra, starting with the smallest key that is + * is strictly greater than before_start + */ + +void ra_append_copies_after(roaring_array_t *ra, const roaring_array_t *sa, + uint16_t before_start, bool copy_on_write); + +/** + * Move the key-value pairs to ra from sa at indexes + * [start_index, end_index), old array should not be freed + * (use ra_clear_without_containers) + **/ +void ra_append_move_range(roaring_array_t *ra, roaring_array_t *sa, + int32_t start_index, int32_t end_index); +/** + * Append new key-value pairs to ra, from sa at indexes + * [start_index, end_index) + */ +void ra_append_range(roaring_array_t *ra, roaring_array_t *sa, + int32_t start_index, int32_t end_index, + bool copy_on_write); + +/** + * Set the container at the corresponding index using the specified + * typecode. + */ +static inline void ra_set_container_at_index(const roaring_array_t *ra, int32_t i, + void *c, uint8_t typecode) { + assert(i < ra->size); + ra->containers[i] = c; + ra->typecodes[i] = typecode; +} + +/** + * If needed, increase the capacity of the array so that it can fit k values + * (at + * least); + */ +bool extend_array(roaring_array_t *ra, int32_t k); + +static inline int32_t ra_get_size(const roaring_array_t *ra) { return ra->size; } + +static inline int32_t ra_advance_until(const roaring_array_t *ra, uint16_t x, + int32_t pos) { + return advanceUntil(ra->keys, pos, ra->size, x); +} + +int32_t ra_advance_until_freeing(roaring_array_t *ra, uint16_t x, int32_t pos); + +void ra_downsize(roaring_array_t *ra, int32_t new_length); + +static inline void ra_replace_key_and_container_at_index(roaring_array_t *ra, + int32_t i, uint16_t key, + void *c, uint8_t typecode) { + assert(i < ra->size); + + ra->keys[i] = key; + ra->containers[i] = c; + ra->typecodes[i] = typecode; +} + +// write set bits to an array +void ra_to_uint32_array(const roaring_array_t *ra, uint32_t *ans); + +bool ra_range_uint32_array(const roaring_array_t *ra, size_t offset, size_t limit, uint32_t *ans); + +/** + * write a bitmap to a buffer. This is meant to be compatible with + * the + * Java and Go versions. Return the size in bytes of the serialized + * output (which should be ra_portable_size_in_bytes(ra)). + */ +size_t ra_portable_serialize(const roaring_array_t *ra, char *buf); + +/** + * read a bitmap from a serialized version. This is meant to be compatible + * with the Java and Go versions. + * maxbytes indicates how many bytes available from buf. + * When the function returns true, roaring_array_t is populated with the data + * and *readbytes indicates how many bytes were read. In all cases, if the function + * returns true, then maxbytes >= *readbytes. + */ +bool ra_portable_deserialize(roaring_array_t *ra, const char *buf, const size_t maxbytes, size_t * readbytes); + +/** + * Quickly checks whether there is a serialized bitmap at the pointer, + * not exceeding size "maxbytes" in bytes. This function does not allocate + * memory dynamically. + * + * This function returns 0 if and only if no valid bitmap is found. + * Otherwise, it returns how many bytes are occupied by the bitmap data. + */ +size_t ra_portable_deserialize_size(const char *buf, const size_t maxbytes); + +/** + * How many bytes are required to serialize this bitmap (meant to be + * compatible + * with Java and Go versions) + */ +size_t ra_portable_size_in_bytes(const roaring_array_t *ra); + +/** + * return true if it contains at least one run container. + */ +bool ra_has_run_container(const roaring_array_t *ra); + +/** + * Size of the header when serializing (meant to be compatible + * with Java and Go versions) + */ +uint32_t ra_portable_header_size(const roaring_array_t *ra); + +/** + * If the container at the index i is share, unshare it (creating a local + * copy if needed). + */ +static inline void ra_unshare_container_at_index(roaring_array_t *ra, + uint16_t i) { + assert(i < ra->size); + ra->containers[i] = + get_writable_copy_if_shared(ra->containers[i], &ra->typecodes[i]); +} + +/** + * remove at index i, sliding over all entries after i + */ +void ra_remove_at_index(roaring_array_t *ra, int32_t i); + + +/** +* clears all containers, sets the size at 0 and shrinks the memory usage. +*/ +void ra_reset(roaring_array_t *ra); + +/** + * remove at index i, sliding over all entries after i. Free removed container. + */ +void ra_remove_at_index_and_free(roaring_array_t *ra, int32_t i); + +/** + * remove a chunk of indices, sliding over entries after it + */ +// void ra_remove_index_range(roaring_array_t *ra, int32_t begin, int32_t end); + +// used in inplace andNot only, to slide left the containers from +// the mutated RoaringBitmap that are after the largest container of +// the argument RoaringBitmap. It is followed by a call to resize. +// +void ra_copy_range(roaring_array_t *ra, uint32_t begin, uint32_t end, + uint32_t new_begin); + +/** + * Shifts rightmost $count containers to the left (distance < 0) or + * to the right (distance > 0). + * Allocates memory if necessary. + * This function doesn't free or create new containers. + * Caller is responsible for that. + */ +void ra_shift_tail(roaring_array_t *ra, int32_t count, int32_t distance); + +#ifdef __cplusplus +} +#endif + +#endif +/* end file include/roaring/roaring_array.h */ +/* begin file include/roaring/roaring.h */ +/* +An implementation of Roaring Bitmaps in C. +*/ + +#ifndef ROARING_H +#define ROARING_H +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct roaring_bitmap_s { + roaring_array_t high_low_container; +} roaring_bitmap_t; + +/** + * Creates a new bitmap (initially empty) + */ +roaring_bitmap_t *roaring_bitmap_create(void); + +/** + * Add all the values between min (included) and max (excluded) that are at a + * distance k*step from min. +*/ +roaring_bitmap_t *roaring_bitmap_from_range(uint64_t min, uint64_t max, + uint32_t step); + +/** + * Creates a new bitmap (initially empty) with a provided + * container-storage capacity (it is a performance hint). + */ +roaring_bitmap_t *roaring_bitmap_create_with_capacity(uint32_t cap); + +/** + * Creates a new bitmap from a pointer of uint32_t integers + */ +roaring_bitmap_t *roaring_bitmap_of_ptr(size_t n_args, const uint32_t *vals); + +/* + * Whether you want to use copy-on-write. + * Saves memory and avoids copies but needs more care in a threaded context. + * Most users should ignore this flag. + * Note: if you do turn this flag to 'true', enabling COW, + * then ensure that you do so for all of your bitmaps since + * interactions between bitmaps with and without COW is unsafe. + */ +static inline bool roaring_bitmap_get_copy_on_write(const roaring_bitmap_t* r) { + return r->high_low_container.flags & ROARING_FLAG_COW; +} +static inline void roaring_bitmap_set_copy_on_write(roaring_bitmap_t* r, bool cow) { + if (cow) { + r->high_low_container.flags |= ROARING_FLAG_COW; + } else { + r->high_low_container.flags &= ~ROARING_FLAG_COW; + } +} + +/** + * Describe the inner structure of the bitmap. + */ +void roaring_bitmap_printf_describe(const roaring_bitmap_t *ra); + +/** + * Creates a new bitmap from a list of uint32_t integers + */ +roaring_bitmap_t *roaring_bitmap_of(size_t n, ...); + +/** + * Copies a bitmap. This does memory allocation. The caller is responsible for + * memory management. + * + */ +roaring_bitmap_t *roaring_bitmap_copy(const roaring_bitmap_t *r); + + +/** + * Copies a bitmap from src to dest. It is assumed that the pointer dest + * is to an already allocated bitmap. The content of the dest bitmap is + * freed/deleted. + * + * It might be preferable and simpler to call roaring_bitmap_copy except + * that roaring_bitmap_overwrite can save on memory allocations. + * + */ +bool roaring_bitmap_overwrite(roaring_bitmap_t *dest, + const roaring_bitmap_t *src); + +/** + * Print the content of the bitmap. + */ +void roaring_bitmap_printf(const roaring_bitmap_t *ra); + +/** + * Computes the intersection between two bitmaps and returns new bitmap. The + * caller is + * responsible for memory management. + * + */ +roaring_bitmap_t *roaring_bitmap_and(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Computes the size of the intersection between two bitmaps. + * + */ +uint64_t roaring_bitmap_and_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + + +/** + * Check whether two bitmaps intersect. + * + */ +bool roaring_bitmap_intersect(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Computes the Jaccard index between two bitmaps. (Also known as the Tanimoto + * distance, + * or the Jaccard similarity coefficient) + * + * The Jaccard index is undefined if both bitmaps are empty. + * + */ +double roaring_bitmap_jaccard_index(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Computes the size of the union between two bitmaps. + * + */ +uint64_t roaring_bitmap_or_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Computes the size of the difference (andnot) between two bitmaps. + * + */ +uint64_t roaring_bitmap_andnot_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Computes the size of the symmetric difference (andnot) between two bitmaps. + * + */ +uint64_t roaring_bitmap_xor_cardinality(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Inplace version modifies x1, x1 == x2 is allowed + */ +void roaring_bitmap_and_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Computes the union between two bitmaps and returns new bitmap. The caller is + * responsible for memory management. + */ +roaring_bitmap_t *roaring_bitmap_or(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Inplace version of roaring_bitmap_or, modifies x1. TDOO: decide whether x1 == + *x2 ok + * + */ +void roaring_bitmap_or_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Compute the union of 'number' bitmaps. See also roaring_bitmap_or_many_heap. + * Caller is responsible for freeing the + * result. + * + */ +roaring_bitmap_t *roaring_bitmap_or_many(size_t number, + const roaring_bitmap_t **x); + +/** + * Compute the union of 'number' bitmaps using a heap. This can + * sometimes be faster than roaring_bitmap_or_many which uses + * a naive algorithm. Caller is responsible for freeing the + * result. + * + */ +roaring_bitmap_t *roaring_bitmap_or_many_heap(uint32_t number, + const roaring_bitmap_t **x); + +/** + * Computes the symmetric difference (xor) between two bitmaps + * and returns new bitmap. The caller is responsible for memory management. + */ +roaring_bitmap_t *roaring_bitmap_xor(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Inplace version of roaring_bitmap_xor, modifies x1. x1 != x2. + * + */ +void roaring_bitmap_xor_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Compute the xor of 'number' bitmaps. + * Caller is responsible for freeing the + * result. + * + */ +roaring_bitmap_t *roaring_bitmap_xor_many(size_t number, + const roaring_bitmap_t **x); + +/** + * Computes the difference (andnot) between two bitmaps + * and returns new bitmap. The caller is responsible for memory management. + */ +roaring_bitmap_t *roaring_bitmap_andnot(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * Inplace version of roaring_bitmap_andnot, modifies x1. x1 != x2. + * + */ +void roaring_bitmap_andnot_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * TODO: consider implementing: + * Compute the xor of 'number' bitmaps using a heap. This can + * sometimes be faster than roaring_bitmap_xor_many which uses + * a naive algorithm. Caller is responsible for freeing the + * result. + * + * roaring_bitmap_t *roaring_bitmap_xor_many_heap(uint32_t number, + * const roaring_bitmap_t **x); + */ + +/** + * Frees the memory. + */ +void roaring_bitmap_free(const roaring_bitmap_t *r); + +/** + * Add value n_args from pointer vals, faster than repeatedly calling + * roaring_bitmap_add + * + */ +void roaring_bitmap_add_many(roaring_bitmap_t *r, size_t n_args, + const uint32_t *vals); + +/** + * Add value x + * + */ +void roaring_bitmap_add(roaring_bitmap_t *r, uint32_t x); + +/** + * Add value x + * Returns true if a new value was added, false if the value was already existing. + */ +bool roaring_bitmap_add_checked(roaring_bitmap_t *r, uint32_t x); + +/** + * Add all values in range [min, max] + */ +void roaring_bitmap_add_range_closed(roaring_bitmap_t *ra, uint32_t min, uint32_t max); + +/** + * Add all values in range [min, max) + */ +static inline void roaring_bitmap_add_range(roaring_bitmap_t *ra, uint64_t min, uint64_t max) { + if(max == min) return; + roaring_bitmap_add_range_closed(ra, (uint32_t)min, (uint32_t)(max - 1)); +} + +/** + * Remove value x + * + */ +void roaring_bitmap_remove(roaring_bitmap_t *r, uint32_t x); + +/** Remove all values in range [min, max] */ +void roaring_bitmap_remove_range_closed(roaring_bitmap_t *ra, uint32_t min, uint32_t max); + +/** Remove all values in range [min, max) */ +static inline void roaring_bitmap_remove_range(roaring_bitmap_t *ra, uint64_t min, uint64_t max) { + if(max == min) return; + roaring_bitmap_remove_range_closed(ra, (uint32_t)min, (uint32_t)(max - 1)); +} + +/** Remove multiple values */ +void roaring_bitmap_remove_many(roaring_bitmap_t *r, size_t n_args, + const uint32_t *vals); + +/** + * Remove value x + * Returns true if a new value was removed, false if the value was not existing. + */ +bool roaring_bitmap_remove_checked(roaring_bitmap_t *r, uint32_t x); + +/** + * Check if value x is present + */ +static inline bool roaring_bitmap_contains(const roaring_bitmap_t *r, uint32_t val) { + const uint16_t hb = val >> 16; + /* + * the next function call involves a binary search and lots of branching. + */ + int32_t i = ra_get_index(&r->high_low_container, hb); + if (i < 0) return false; + + uint8_t typecode; + // next call ought to be cheap + void *container = + ra_get_container_at_index(&r->high_low_container, i, &typecode); + // rest might be a tad expensive, possibly involving another round of binary search + return container_contains(container, val & 0xFFFF, typecode); +} + +/** + * Check whether a range of values from range_start (included) to range_end (excluded) is present + */ +bool roaring_bitmap_contains_range(const roaring_bitmap_t *r, uint64_t range_start, uint64_t range_end); + +/** + * Get the cardinality of the bitmap (number of elements). + */ +uint64_t roaring_bitmap_get_cardinality(const roaring_bitmap_t *ra); + +/** + * Returns the number of elements in the range [range_start, range_end). + */ +uint64_t roaring_bitmap_range_cardinality(const roaring_bitmap_t *ra, + uint64_t range_start, uint64_t range_end); + +/** +* Returns true if the bitmap is empty (cardinality is zero). +*/ +bool roaring_bitmap_is_empty(const roaring_bitmap_t *ra); + + +/** +* Empties the bitmap +*/ +void roaring_bitmap_clear(roaring_bitmap_t *ra); + +/** + * Convert the bitmap to an array. Write the output to "ans", + * caller is responsible to ensure that there is enough memory + * allocated + * (e.g., ans = malloc(roaring_bitmap_get_cardinality(mybitmap) + * * sizeof(uint32_t)) + */ +void roaring_bitmap_to_uint32_array(const roaring_bitmap_t *ra, uint32_t *ans); + + +/** + * Convert the bitmap to an array from "offset" by "limit". Write the output to "ans". + * so, you can get data in paging. + * caller is responsible to ensure that there is enough memory + * allocated + * (e.g., ans = malloc(roaring_bitmap_get_cardinality(limit) + * * sizeof(uint32_t)) + * Return false in case of failure (e.g., insufficient memory) + */ +bool roaring_bitmap_range_uint32_array(const roaring_bitmap_t *ra, size_t offset, size_t limit, uint32_t *ans); + +/** + * Remove run-length encoding even when it is more space efficient + * return whether a change was applied + */ +bool roaring_bitmap_remove_run_compression(roaring_bitmap_t *r); + +/** convert array and bitmap containers to run containers when it is more + * efficient; + * also convert from run containers when more space efficient. Returns + * true if the result has at least one run container. + * Additional savings might be possible by calling shrinkToFit(). + */ +bool roaring_bitmap_run_optimize(roaring_bitmap_t *r); + +/** + * If needed, reallocate memory to shrink the memory usage. Returns + * the number of bytes saved. +*/ +size_t roaring_bitmap_shrink_to_fit(roaring_bitmap_t *r); + +/** +* write the bitmap to an output pointer, this output buffer should refer to +* at least roaring_bitmap_size_in_bytes(ra) allocated bytes. +* +* see roaring_bitmap_portable_serialize if you want a format that's compatible +* with Java and Go implementations +* +* this format has the benefit of being sometimes more space efficient than +* roaring_bitmap_portable_serialize +* e.g., when the data is sparse. +* +* Returns how many bytes were written which should be +* roaring_bitmap_size_in_bytes(ra). +*/ +size_t roaring_bitmap_serialize(const roaring_bitmap_t *ra, char *buf); + +/** use with roaring_bitmap_serialize +* see roaring_bitmap_portable_deserialize if you want a format that's +* compatible with Java and Go implementations +*/ +roaring_bitmap_t *roaring_bitmap_deserialize(const void *buf); + +/** + * How many bytes are required to serialize this bitmap (NOT compatible + * with Java and Go versions) + */ +size_t roaring_bitmap_size_in_bytes(const roaring_bitmap_t *ra); + +/** + * read a bitmap from a serialized version. This is meant to be compatible with + * the Java and Go versions. See format specification at + * https://github.com/RoaringBitmap/RoaringFormatSpec + * In case of failure, a null pointer is returned. + * This function is unsafe in the sense that if there is no valid serialized + * bitmap at the pointer, then many bytes could be read, possibly causing a buffer + * overflow. For a safer approach, + * call roaring_bitmap_portable_deserialize_safe. + */ +roaring_bitmap_t *roaring_bitmap_portable_deserialize(const char *buf); + +/** + * read a bitmap from a serialized version in a safe manner (reading up to maxbytes). + * This is meant to be compatible with + * the Java and Go versions. See format specification at + * https://github.com/RoaringBitmap/RoaringFormatSpec + * In case of failure, a null pointer is returned. + */ +roaring_bitmap_t *roaring_bitmap_portable_deserialize_safe(const char *buf, size_t maxbytes); + +/** + * Check how many bytes would be read (up to maxbytes) at this pointer if there + * is a bitmap, returns zero if there is no valid bitmap. + * This is meant to be compatible with + * the Java and Go versions. See format specification at + * https://github.com/RoaringBitmap/RoaringFormatSpec + */ +size_t roaring_bitmap_portable_deserialize_size(const char *buf, size_t maxbytes); + + +/** + * How many bytes are required to serialize this bitmap (meant to be compatible + * with Java and Go versions). See format specification at + * https://github.com/RoaringBitmap/RoaringFormatSpec + */ +size_t roaring_bitmap_portable_size_in_bytes(const roaring_bitmap_t *ra); + +/** + * write a bitmap to a char buffer. The output buffer should refer to at least + * roaring_bitmap_portable_size_in_bytes(ra) bytes of allocated memory. + * This is meant to be compatible with + * the + * Java and Go versions. Returns how many bytes were written which should be + * roaring_bitmap_portable_size_in_bytes(ra). See format specification at + * https://github.com/RoaringBitmap/RoaringFormatSpec + */ +size_t roaring_bitmap_portable_serialize(const roaring_bitmap_t *ra, char *buf); + +/* + * "Frozen" serialization format imitates memory layout of roaring_bitmap_t. + * Deserialized bitmap is a constant view of the underlying buffer. + * This significantly reduces amount of allocations and copying required during + * deserialization. + * It can be used with memory mapped files. + * Example can be found in benchmarks/frozen_benchmark.c + * + * [#####] const roaring_bitmap_t * + * | | | + * +----+ | +-+ + * | | | + * [#####################################] underlying buffer + * + * Note that because frozen serialization format imitates C memory layout + * of roaring_bitmap_t, it is not fixed. It is different on big/little endian + * platforms and can be changed in future. + */ + +/** + * Returns number of bytes required to serialize bitmap using frozen format. + */ +size_t roaring_bitmap_frozen_size_in_bytes(const roaring_bitmap_t *ra); + +/** + * Serializes bitmap using frozen format. + * Buffer size must be at least roaring_bitmap_frozen_size_in_bytes(). + */ +void roaring_bitmap_frozen_serialize(const roaring_bitmap_t *ra, char *buf); + +/** + * Creates constant bitmap that is a view of a given buffer. + * Buffer must contain data previously written by roaring_bitmap_frozen_serialize(), + * and additionally its beginning must be aligned by 32 bytes. + * Length must be equal exactly to roaring_bitmap_frozen_size_in_bytes(). + * + * On error, NULL is returned. + * + * Bitmap returned by this function can be used in all readonly contexts. + * Bitmap must be freed as usual, by calling roaring_bitmap_free(). + * Underlying buffer must not be freed or modified while it backs any bitmaps. + */ +const roaring_bitmap_t *roaring_bitmap_frozen_view(const char *buf, size_t length); + + +/** + * Iterate over the bitmap elements. The function iterator is called once for + * all the values with ptr (can be NULL) as the second parameter of each call. + * + * roaring_iterator is simply a pointer to a function that returns bool + * (true means that the iteration should continue while false means that it + * should stop), + * and takes (uint32_t,void*) as inputs. + * + * Returns true if the roaring_iterator returned true throughout (so that + * all data points were necessarily visited). + */ +bool roaring_iterate(const roaring_bitmap_t *ra, roaring_iterator iterator, + void *ptr); + +bool roaring_iterate64(const roaring_bitmap_t *ra, roaring_iterator64 iterator, + uint64_t high_bits, void *ptr); + +/** + * Return true if the two bitmaps contain the same elements. + */ +bool roaring_bitmap_equals(const roaring_bitmap_t *ra1, + const roaring_bitmap_t *ra2); + +/** + * Return true if all the elements of ra1 are also in ra2. + */ +bool roaring_bitmap_is_subset(const roaring_bitmap_t *ra1, + const roaring_bitmap_t *ra2); + +/** + * Return true if all the elements of ra1 are also in ra2 and ra2 is strictly + * greater + * than ra1. + */ +bool roaring_bitmap_is_strict_subset(const roaring_bitmap_t *ra1, + const roaring_bitmap_t *ra2); + +/** + * (For expert users who seek high performance.) + * + * Computes the union between two bitmaps and returns new bitmap. The caller is + * responsible for memory management. + * + * The lazy version defers some computations such as the maintenance of the + * cardinality counts. Thus you need + * to call roaring_bitmap_repair_after_lazy after executing "lazy" computations. + * It is safe to repeatedly call roaring_bitmap_lazy_or_inplace on the result. + * The bitsetconversion conversion is a flag which determines + * whether container-container operations force a bitset conversion. + **/ +roaring_bitmap_t *roaring_bitmap_lazy_or(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2, + const bool bitsetconversion); + +/** + * (For expert users who seek high performance.) + * Inplace version of roaring_bitmap_lazy_or, modifies x1 + * The bitsetconversion conversion is a flag which determines + * whether container-container operations force a bitset conversion. + */ +void roaring_bitmap_lazy_or_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2, + const bool bitsetconversion); + +/** + * (For expert users who seek high performance.) + * + * Execute maintenance operations on a bitmap created from + * roaring_bitmap_lazy_or + * or modified with roaring_bitmap_lazy_or_inplace. + */ +void roaring_bitmap_repair_after_lazy(roaring_bitmap_t *x1); + +/** + * Computes the symmetric difference between two bitmaps and returns new bitmap. + *The caller is + * responsible for memory management. + * + * The lazy version defers some computations such as the maintenance of the + * cardinality counts. Thus you need + * to call roaring_bitmap_repair_after_lazy after executing "lazy" computations. + * It is safe to repeatedly call roaring_bitmap_lazy_xor_inplace on the result. + * + */ +roaring_bitmap_t *roaring_bitmap_lazy_xor(const roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * (For expert users who seek high performance.) + * Inplace version of roaring_bitmap_lazy_xor, modifies x1. x1 != x2 + * + */ +void roaring_bitmap_lazy_xor_inplace(roaring_bitmap_t *x1, + const roaring_bitmap_t *x2); + +/** + * compute the negation of the roaring bitmap within a specified + * interval: [range_start, range_end). The number of negated values is + * range_end - range_start. + * Areas outside the range are passed through unchanged. + */ + +roaring_bitmap_t *roaring_bitmap_flip(const roaring_bitmap_t *x1, + uint64_t range_start, uint64_t range_end); + +/** + * compute (in place) the negation of the roaring bitmap within a specified + * interval: [range_start, range_end). The number of negated values is + * range_end - range_start. + * Areas outside the range are passed through unchanged. + */ + +void roaring_bitmap_flip_inplace(roaring_bitmap_t *x1, uint64_t range_start, + uint64_t range_end); + +/** + * Selects the element at index 'rank' where the smallest element is at index 0. + * If the size of the roaring bitmap is strictly greater than rank, then this + function returns true and sets element to the element of given rank. + Otherwise, it returns false. + */ +bool roaring_bitmap_select(const roaring_bitmap_t *ra, uint32_t rank, + uint32_t *element); +/** +* roaring_bitmap_rank returns the number of integers that are smaller or equal +* to x. Thus if x is the first element, this function will return 1. If +* x is smaller than the smallest element, this function will return 0. +* +* The indexing convention differs between roaring_bitmap_select and +* roaring_bitmap_rank: roaring_bitmap_select refers to the smallest value +* as having index 0, whereas roaring_bitmap_rank returns 1 when ranking +* the smallest value. +*/ +uint64_t roaring_bitmap_rank(const roaring_bitmap_t *bm, uint32_t x); + +/** +* roaring_bitmap_smallest returns the smallest value in the set. +* Returns UINT32_MAX if the set is empty. +*/ +uint32_t roaring_bitmap_minimum(const roaring_bitmap_t *bm); + +/** +* roaring_bitmap_smallest returns the greatest value in the set. +* Returns 0 if the set is empty. +*/ +uint32_t roaring_bitmap_maximum(const roaring_bitmap_t *bm); + +/** +* (For advanced users.) +* Collect statistics about the bitmap, see roaring_types.h for +* a description of roaring_statistics_t +*/ +void roaring_bitmap_statistics(const roaring_bitmap_t *ra, + roaring_statistics_t *stat); + +/********************* +* What follows is code use to iterate through values in a roaring bitmap + +roaring_bitmap_t *ra =... +roaring_uint32_iterator_t i; +roaring_create_iterator(ra, &i); +while(i.has_value) { + printf("value = %d\n", i.current_value); + roaring_advance_uint32_iterator(&i); +} + +Obviously, if you modify the underlying bitmap, the iterator +becomes invalid. So don't. +*/ + +typedef struct roaring_uint32_iterator_s { + const roaring_bitmap_t *parent; // owner + int32_t container_index; // point to the current container index + int32_t in_container_index; // for bitset and array container, this is out + // index + int32_t run_index; // for run container, this points at the run + + uint32_t current_value; + bool has_value; + + const void + *container; // should be: + // parent->high_low_container.containers[container_index]; + uint8_t typecode; // should be: + // parent->high_low_container.typecodes[container_index]; + uint32_t highbits; // should be: + // parent->high_low_container.keys[container_index]) << + // 16; + +} roaring_uint32_iterator_t; + +/** +* Initialize an iterator object that can be used to iterate through the +* values. If there is a value, then this iterator points to the first value +* and it->has_value is true. The value is in it->current_value. +*/ +void roaring_init_iterator(const roaring_bitmap_t *ra, + roaring_uint32_iterator_t *newit); + +/** +* Initialize an iterator object that can be used to iterate through the +* values. If there is a value, then this iterator points to the last value +* and it->has_value is true. The value is in it->current_value. +*/ +void roaring_init_iterator_last(const roaring_bitmap_t *ra, + roaring_uint32_iterator_t *newit); + +/** +* Create an iterator object that can be used to iterate through the +* values. Caller is responsible for calling roaring_free_iterator. +* The iterator is initialized. If there is a value, then this iterator +* points to the first value and it->has_value is true. +* The value is in it->current_value. +* +* This function calls roaring_init_iterator. +*/ +roaring_uint32_iterator_t *roaring_create_iterator(const roaring_bitmap_t *ra); + +/** +* Advance the iterator. If there is a new value, then it->has_value is true. +* The new value is in it->current_value. Values are traversed in increasing +* orders. For convenience, returns it->has_value. +*/ +bool roaring_advance_uint32_iterator(roaring_uint32_iterator_t *it); + +/** +* Decrement the iterator. If there is a new value, then it->has_value is true. +* The new value is in it->current_value. Values are traversed in decreasing +* orders. For convenience, returns it->has_value. +*/ +bool roaring_previous_uint32_iterator(roaring_uint32_iterator_t *it); + +/** +* Move the iterator to the first value >= val. If there is a such a value, then it->has_value is true. +* The new value is in it->current_value. For convenience, returns it->has_value. +*/ +bool roaring_move_uint32_iterator_equalorlarger(roaring_uint32_iterator_t *it, uint32_t val) ; +/** +* Creates a copy of an iterator. +* Caller must free it. +*/ +roaring_uint32_iterator_t *roaring_copy_uint32_iterator( + const roaring_uint32_iterator_t *it); + +/** +* Free memory following roaring_create_iterator +*/ +void roaring_free_uint32_iterator(roaring_uint32_iterator_t *it); + +/* + * Reads next ${count} values from iterator into user-supplied ${buf}. + * Returns the number of read elements. + * This number can be smaller than ${count}, which means that iterator is drained. + * + * This function satisfies semantics of iteration and can be used together with + * other iterator functions. + * - first value is copied from ${it}->current_value + * - after function returns, iterator is positioned at the next element + */ +uint32_t roaring_read_uint32_iterator(roaring_uint32_iterator_t *it, uint32_t* buf, uint32_t count); + +#ifdef __cplusplus +} +#endif + +#endif +/* end file include/roaring/roaring.h */ diff --git a/src/libsysprof/demangle.cpp b/contrib/elfparser/demangle.cpp similarity index 100% rename from src/libsysprof/demangle.cpp rename to contrib/elfparser/demangle.cpp diff --git a/src/libsysprof/demangle.h b/contrib/elfparser/demangle.h similarity index 100% rename from src/libsysprof/demangle.h rename to contrib/elfparser/demangle.h diff --git a/src/libsysprof/elfparser.c b/contrib/elfparser/elfparser.c similarity index 97% rename from src/libsysprof/elfparser.c rename to contrib/elfparser/elfparser.c index 8816a716..2c6b638b 100644 --- a/src/libsysprof/elfparser.c +++ b/contrib/elfparser/elfparser.c @@ -297,6 +297,37 @@ open_mapped_file (const char *filename, return file; } +ElfParser * +elf_parser_new_from_mmap (GMappedFile *file, + GError **error) +{ + const guchar *data; + gsize length; + ElfParser *parser; + + if (file == NULL) + return NULL; + + data = (guchar *)g_mapped_file_get_contents (file); + length = g_mapped_file_get_length (file); + parser = elf_parser_new_from_data (data, length); + + if (!parser) + { + g_set_error (error, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + "Failed to load ELF from mmap region"); + g_mapped_file_unref (file); + return NULL; + } + + parser->filename = NULL; + parser->file = file; + + return parser; +} + ElfParser * elf_parser_new (const char *filename, GError **error) diff --git a/src/libsysprof/elfparser.h b/contrib/elfparser/elfparser.h similarity index 95% rename from src/libsysprof/elfparser.h rename to contrib/elfparser/elfparser.h index 94fa5c02..fd19e78d 100644 --- a/src/libsysprof/elfparser.h +++ b/contrib/elfparser/elfparser.h @@ -27,6 +27,8 @@ typedef struct ElfParser ElfParser; ElfParser *elf_parser_new_from_data (const guchar *data, gsize length); +ElfParser *elf_parser_new_from_mmap (GMappedFile *mapped_file, + GError **err); ElfParser *elf_parser_new (const char *filename, GError **err); void elf_parser_free (ElfParser *parser); diff --git a/contrib/elfparser/meson.build b/contrib/elfparser/meson.build new file mode 100644 index 00000000..b3c75e6b --- /dev/null +++ b/contrib/elfparser/meson.build @@ -0,0 +1,19 @@ +libelfparser_sources = [ + 'demangle.cpp', + 'elfparser.c', +] + +libelfparser_deps = [ + dependency('glib-2.0', version: glib_req_version), +] + +libelfparser_static = static_library('elfparser', libelfparser_sources, + dependencies: libelfparser_deps, + gnu_symbol_visibility: 'hidden', +) + +libelfparser_static_dep = declare_dependency( + include_directories: include_directories('.'), + dependencies: libelfparser_deps, + link_with: libelfparser_static, +) diff --git a/contrib/linereader/line-reader-private.h b/contrib/linereader/line-reader-private.h new file mode 100644 index 00000000..deea4396 --- /dev/null +++ b/contrib/linereader/line-reader-private.h @@ -0,0 +1,94 @@ +/* line-reader-private.h + * + * Copyright 2015-2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +typedef struct _LineReader +{ + char *contents; + gsize length; + gssize pos; +} LineReader; + +static inline void +line_reader_init (LineReader *reader, + char *contents, + gssize length) +{ + g_assert (reader != NULL); + + if (length < 0) + length = strlen (contents); + + if (contents != NULL) + { + reader->contents = contents; + reader->length = length; + reader->pos = 0; + } + else + { + reader->contents = NULL; + reader->length = 0; + reader->pos = 0; + } +} + +static inline char * +line_reader_next (LineReader *reader, + gsize *length) +{ + char *ret = NULL; + + g_assert (reader != NULL); + g_assert (length != NULL); + + if ((reader->contents == NULL) || (reader->pos >= reader->length)) + { + *length = 0; + return NULL; + } + + ret = &reader->contents [reader->pos]; + + for (; reader->pos < reader->length; reader->pos++) + { + if (reader->contents [reader->pos] == '\n') + { + *length = &reader->contents [reader->pos] - ret; + /* Ingore the \r in \r\n if provided */ + if (*length > 0 && reader->pos > 0 && reader->contents [reader->pos - 1] == '\r') + (*length)--; + reader->pos++; + return ret; + } + } + + *length = &reader->contents [reader->pos] - ret; + + return ret; +} + +G_END_DECLS diff --git a/contrib/linereader/meson.build b/contrib/linereader/meson.build new file mode 100644 index 00000000..9055b71e --- /dev/null +++ b/contrib/linereader/meson.build @@ -0,0 +1,7 @@ +liblinereader_deps = [ + dependency('gio-2.0', version: glib_req_version), +] + +liblinereader_static_dep = declare_dependency( + include_directories: include_directories('.'), +) diff --git a/contrib/meson.build b/contrib/meson.build new file mode 100644 index 00000000..574a013a --- /dev/null +++ b/contrib/meson.build @@ -0,0 +1,3 @@ +subdir('eggbitset') +subdir('elfparser') +subdir('linereader') diff --git a/meson.build b/meson.build index 7a8edf33..f41bc27c 100644 --- a/meson.build +++ b/meson.build @@ -1,9 +1,9 @@ -project('sysprof', 'c', +project('sysprof', ['c', 'cpp'], license: ['GPL3+', 'GPL2+'], version: '45.alpha', meson_version: '>=0.59.0', - default_options: [ 'c_std=gnu11', - 'cpp_std=c++11', + default_options: [ 'c_std=gnu17', + 'cpp_std=gnu++17', 'warning_level=2', ] ) @@ -20,28 +20,48 @@ else app_id = 'org.gnome.Sysprof' endif -libsysprof_api_version = 4 -libsysprof_ui_api_version = 5 +# All libraries share an ABI revision as they are expected +# to be updated as a set. However, we keep libsysprof-capture +# at an older version since it's used as a static library +# from various platform tooling +soname_major_version = 6 +libsysprof_capture_api_version = 4 +libsysprof_api_version = soname_major_version version_split = meson.project_version().split('.') datadir = get_option('datadir') datadir_for_pc_file = join_paths('${prefix}', datadir) podir = join_paths(meson.current_source_dir(), 'po') -glib_req = '2.73.0' -gtk_req = '4.6' +# Predetermine some features based on meson_options.txt +need_gtk = get_option('gtk') +need_glib = (need_gtk or + get_option('examples') or + get_option('sysprofd') != 'none' or + get_option('tools') or + get_option('tests')) +need_libsysprof = (need_gtk or + get_option('libsysprof') or + get_option('examples') or + get_option('tools') or + get_option('tests')) + +dex_req = '0.3' +glib_req = '2.76.0' +gtk_req = '4.10' polkit_req = '0.105' +dex_req_version = '>= @0@'.format(dex_req) glib_req_version = '>= @0@'.format(glib_req) gtk_req_version = '>= @0@'.format(gtk_req) polkit_req_version = '>= @0@'.format(polkit_req) cc = meson.get_compiler('c') +cxx = meson.get_compiler('cpp') -if get_option('libsysprof') or get_option('agent') - add_languages('cpp', native: false) - cxx = meson.get_compiler('cpp') -endif +glib_dep = dependency('glib-2.0', version: glib_req_version, required: need_glib) +gtk_dep = dependency('gtk4', version: gtk_req_version, required: need_gtk) +libsystemd_dep = dependency('libsystemd', required: false) config_h = configuration_data() config_h.set_quoted('SYMBOLIC_VERSION', symbolic_version) @@ -72,25 +92,19 @@ if get_option('default_library') != 'static' endif endif -debugdir = get_option('debugdir') -if debugdir == '' - debugdir = join_paths(get_option('prefix'), get_option('libdir'), 'debug') -endif -config_h.set_quoted('DEBUGDIR', debugdir) - -config_h.set_quoted('GETTEXT_PACKAGE', 'sysprof') -config_h.set10('ENABLE_NLS', true) -config_h.set_quoted('PACKAGE_LOCALE_DIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale')) -config_h.set('LOCALEDIR', 'PACKAGE_LOCALE_DIR') config_h.set('HAVE_EXECINFO_H', cc.has_header('execinfo.h')) - -config_h.set('HAVE_STRLCPY', cc.has_function('strlcpy')) config_h.set('HAVE_REALLOCARRAY', cc.has_function('reallocarray')) +config_h.set('HAVE_STRLCPY', cc.has_function('strlcpy')) +config_h.set('LOCALEDIR', 'PACKAGE_LOCALE_DIR') +config_h.set10('ENABLE_NLS', true) +config_h.set_quoted('GETTEXT_PACKAGE', 'sysprof') +config_h.set_quoted('PACKAGE_LOCALE_DIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale')) +config_h.set10('HAVE_LIBSYSTEMD', libsystemd_dep.found()) polkit_agent_dep = dependency('polkit-agent-1', required: false) -polkit_dep = dependency('polkit-gobject-1', version: polkit_req_version, required: false) - config_h.set10('HAVE_POLKIT_AGENT', polkit_agent_dep.found()) + +polkit_dep = dependency('polkit-gobject-1', version: polkit_req_version, required: false) config_h.set10('HAVE_POLKIT', polkit_dep.found()) if get_option('libunwind') @@ -98,7 +112,11 @@ if get_option('libunwind') # and backtrace() showing up in builds libunwind_dep = dependency('libunwind-generic', required: true) config_h.set('ENABLE_LIBUNWIND', libunwind_dep.found()) - config_h.set('HAVE_UNW_SET_CACHE_SIZE', libunwind_dep.found() and cc.has_header_symbol('libunwind.h', 'unw_set_cache_size', dependencies: [libunwind_dep])) + config_h.set('HAVE_UNW_SET_CACHE_SIZE', + (libunwind_dep.found() and + cc.has_header_symbol('libunwind.h', + 'unw_set_cache_size', + dependencies: [libunwind_dep]))) endif # Development build setup @@ -106,36 +124,49 @@ if get_option('development') config_h.set10('DEVELOPMENT_BUILD', true) endif -has_use_clockid = cc.has_member('struct perf_event_attr', 'use_clockid', prefix: '#include ') -has_clockid = cc.has_member('struct perf_event_attr', 'clockid', prefix: '#include ') +has_use_clockid = cc.has_member('struct perf_event_attr', + 'use_clockid', + prefix: '#include ') +has_clockid = cc.has_member('struct perf_event_attr', + 'clockid', prefix: + '#include ') config_h.set('HAVE_PERF_CLOCKID', has_use_clockid and has_clockid) -add_project_arguments([ - '-I' + meson.current_build_dir(), # config.h -], language: 'c') - -glib_major = glib_req.split('.')[0].to_int() -glib_minor = glib_req.split('.')[1].to_int() -gtk_major = gtk_req.split('.')[0].to_int() -gtk_minor = gtk_req.split('.')[1].to_int() - -if glib_minor % 2 == 1 - glib_minor = glib_minor + 1 -endif -if gtk_minor % 2 == 1 - gtk_minor = gtk_minor + 1 -endif - +# For config.h +add_project_arguments(['-I'+meson.current_build_dir()], language: 'c') global_c_args = [ + '-DSYSPROF_COMPILATION', '-D_GNU_SOURCE', '-D_POSIX_C_SOURCE=200809L', - '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_@0@_@1@'.format(glib_major, glib_minor), - '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_@0@_@1@'.format(glib_major, glib_minor), - '-DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_@0@_@1@'.format(gtk_major, gtk_minor), - '-DGDK_VERSION_MAX_ALLOWED=GDK_VERSION_@0@_@1@'.format(gtk_major, gtk_minor), ] +# Enforce GLib symbol access by required version +if need_glib + glib_major = glib_req.split('.')[0].to_int() + glib_minor = glib_req.split('.')[1].to_int() + if glib_minor % 2 == 1 + glib_minor = glib_minor + 1 + endif + global_c_args += [ + '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_@0@_@1@'.format(glib_major, glib_minor), + '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_@0@_@1@'.format(glib_major, glib_minor), + ] +endif + +# Enforce GTK symbol access by required version +if need_gtk + gtk_major = gtk_req.split('.')[0].to_int() + gtk_minor = gtk_req.split('.')[1].to_int() + if gtk_minor % 2 == 1 + gtk_minor = gtk_minor + 1 + endif + global_c_args += [ + '-DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_@0@_@1@'.format(gtk_major, gtk_minor), + '-DGDK_VERSION_MAX_ALLOWED=GDK_VERSION_@0@_@1@'.format(gtk_major, gtk_minor), + ] +endif + if host_machine.system() == 'darwin' global_c_args += ['-D_DARWIN_C_SOURCE'] endif @@ -155,7 +186,7 @@ test_c_args = [ '-Wswitch-default', '-Wswitch-enum', '-Wuninitialized', - ['-Werror=format-security', '-Werror=format=2' ], + ['-Werror=format-security', '-Werror=format=2'], '-Werror=empty-body', '-Werror=implicit-function-declaration', '-Werror=pointer-arith', @@ -171,13 +202,6 @@ test_c_args = [ '-Werror=undef', ] -# Until GLib is fixed with regards to volatile type registration -if cc.get_id() == 'clang' - test_c_args += ['-Wno-incompatible-pointer-types'] -else - test_c_args += ['-Werror=incompatible-pointer-types'] -endif - foreach arg: test_c_args if cc.has_multi_arguments(arg) global_c_args += arg @@ -229,8 +253,9 @@ int main(void) { config_h.set10('HAVE_STDATOMIC_H', true) endif -needs_service_access = get_option('libsysprof') or get_option('agent') -install_service_files = needs_service_access or get_option('sysprofd') == 'bundled' +if need_glib + subdir('contrib') +endif subdir('src') subdir('data') diff --git a/meson_options.txt b/meson_options.txt index 100ec6b8..d36570ad 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,9 +8,6 @@ option('development', type: 'boolean', value: 'false') # server scenarios. option('gtk', type: 'boolean') -# Disable libsysprof/ui (in situations you only want sysprof-capture) -option('libsysprof', type: 'boolean') - # Allow disabling the installation of libsysprof-capture*.a option('install-static', type: 'boolean') @@ -29,18 +26,15 @@ option('systemdunitdir', type: 'string', description: 'Directory for systemd service files' ) -# An optional location to specify where to locate debug information. This -# is useful for distributions to set based on their debuginfo setup. -option('debugdir', type: 'string', - description: 'Look for global separate debug info in this path' -) - # If Yelp documentation should be installed option('help', type: 'boolean') # Disable use of libunwind option('libunwind', type: 'boolean') +# Build libsysprof (required by tools, tests, sysprof, etc) +option('libsysprof', type: 'boolean') + # Optionally disable the tools (this is mostly only useful for building only # libsysprof-capture as a subproject) option('tools', type: 'boolean') @@ -52,7 +46,3 @@ option('tests', type: 'boolean') # Optionally disable the examples (this is mostly only useful for building only # libsysprof-capture as a subproject) option('examples', type: 'boolean') - -# Optionally build the sysprof-agent tool to allow profiling inside of -# containers from external system -option('agent', type: 'boolean', description: 'Build the sysprof-agent utility') diff --git a/org.gnome.Sysprof.Devel.json b/org.gnome.Sysprof.Devel.json index cf24683f..1b172493 100644 --- a/org.gnome.Sysprof.Devel.json +++ b/org.gnome.Sysprof.Devel.json @@ -98,6 +98,25 @@ } ] }, + { + "name" : "libdex", + "buildsystem" : "meson", + "config-opts" : [ + "--buildtype=debugoptimized", + "-Ddocs=false", + "-Dintrospection=enabled", + "-Dexamples=false", + "-Dtests=false", + "-Dsysprof=false" + ], + "sources" : [ + { + "type" : "git", + "url" : "https://gitlab.gnome.org/chergert/libdex.git", + "branch" : "main" + } + ] + }, { "name" : "sysprof", "config-opts" : [ diff --git a/src/libsysprof-capture/mapped-ring-buffer.c b/src/libsysprof-capture/mapped-ring-buffer.c index c7f2ce2c..ef55c1c1 100644 --- a/src/libsysprof-capture/mapped-ring-buffer.c +++ b/src/libsysprof-capture/mapped-ring-buffer.c @@ -335,6 +335,8 @@ mapped_ring_buffer_finalize (MappedRingBuffer *self) close (self->fd); self->fd = -1; } + + free (self); } void diff --git a/src/libsysprof-capture/meson.build b/src/libsysprof-capture/meson.build index f548f827..aa75b636 100644 --- a/src/libsysprof-capture/meson.build +++ b/src/libsysprof-capture/meson.build @@ -19,8 +19,11 @@ if not meson.is_subproject() install_headers(libsysprof_capture_headers, subdir: sysprof_header_subdir) endif -libsysprof_capture_sources = files([ +mapped_ring_buffer_sources = files([ 'mapped-ring-buffer.c', +]) + +libsysprof_capture_sources = files([ 'sysprof-address.c', 'sysprof-capture-condition.c', 'sysprof-capture-cursor.c', @@ -46,8 +49,9 @@ libsysprof_capture_deps = [ ] libsysprof_capture = static_library( - 'sysprof-capture-@0@'.format(libsysprof_api_version), - libsysprof_capture_sources, + 'sysprof-capture-@0@'.format(libsysprof_capture_api_version), + (libsysprof_capture_sources + + mapped_ring_buffer_sources), dependencies: libsysprof_capture_deps, c_args: [ '-DSYSPROF_CAPTURE_COMPILATION' ], @@ -64,7 +68,7 @@ libsysprof_capture_dep = declare_dependency( dependencies: libsysprof_capture_deps, include_directories: libsysprof_capture_include_dirs, ) -meson.override_dependency('sysprof-capture-@0@'.format(libsysprof_api_version), libsysprof_capture_dep) +meson.override_dependency('sysprof-capture-@0@'.format(libsysprof_capture_api_version), libsysprof_capture_dep) if install_static pkgconfig.generate( @@ -76,3 +80,7 @@ if install_static libraries_private: libsysprof_capture_deps, ) endif + +if get_option('tests') + subdir('tests') +endif diff --git a/src/libsysprof-capture/sysprof-capture-cursor.c b/src/libsysprof-capture/sysprof-capture-cursor.c index 7697ad48..376e8603 100644 --- a/src/libsysprof-capture/sysprof-capture-cursor.c +++ b/src/libsysprof-capture/sysprof-capture-cursor.c @@ -175,6 +175,10 @@ sysprof_capture_cursor_foreach (SysprofCaptureCursor *self, delegate = READ_DELEGATE (sysprof_capture_reader_read_sample); break; + case SYSPROF_CAPTURE_FRAME_TRACE: + delegate = READ_DELEGATE (sysprof_capture_reader_read_trace); + break; + case SYSPROF_CAPTURE_FRAME_LOG: delegate = READ_DELEGATE (sysprof_capture_reader_read_log); break; diff --git a/src/libsysprof-capture/sysprof-capture-reader.c b/src/libsysprof-capture/sysprof-capture-reader.c index 8f5e02e9..6227af9b 100644 --- a/src/libsysprof-capture/sysprof-capture-reader.c +++ b/src/libsysprof-capture/sysprof-capture-reader.c @@ -925,6 +925,52 @@ sysprof_capture_reader_read_sample (SysprofCaptureReader *self) return sample; } +const SysprofCaptureTrace * +sysprof_capture_reader_read_trace (SysprofCaptureReader *self) +{ + SysprofCaptureTrace *trace; + + assert (self != NULL); + assert ((self->pos % SYSPROF_CAPTURE_ALIGN) == 0); + assert (self->pos <= self->bufsz); + + if (!sysprof_capture_reader_ensure_space_for (self, sizeof *trace)) + return NULL; + + trace = (SysprofCaptureTrace *)(void *)&self->buf[self->pos]; + + sysprof_capture_reader_bswap_frame (self, &trace->frame); + + if (trace->frame.type != SYSPROF_CAPTURE_FRAME_TRACE) + return NULL; + + if (trace->frame.len < sizeof *trace) + return NULL; + + if (self->endian != __BYTE_ORDER) + trace->n_addrs = bswap_16 (trace->n_addrs); + + if (trace->frame.len < (sizeof *trace + (sizeof(SysprofCaptureAddress) * trace->n_addrs))) + return NULL; + + if (!sysprof_capture_reader_ensure_space_for (self, trace->frame.len)) + return NULL; + + trace = (SysprofCaptureTrace *)(void *)&self->buf[self->pos]; + + if (SYSPROF_UNLIKELY (self->endian != __BYTE_ORDER)) + { + unsigned int i; + + for (i = 0; i < trace->n_addrs; i++) + trace->addrs[i] = bswap_64 (trace->addrs[i]); + } + + self->pos += trace->frame.len; + + return trace; +} + const SysprofCaptureCounterDefine * sysprof_capture_reader_read_counter_define (SysprofCaptureReader *self) { @@ -1343,7 +1389,7 @@ array_append (const char ***files, *files = new_files; } - (*files)[*n_files] = new_element ? strdup (new_element) : NULL; + (*files)[*n_files] = new_element ? sysprof_strdup (new_element) : NULL; *n_files = *n_files + 1; assert (*n_files <= *n_files_allocated); @@ -1362,7 +1408,10 @@ array_deduplicate (const char **files, for (last_written = 0, next_to_read = 1; last_written <= next_to_read && next_to_read < *n_files;) { if (strcmp (files[next_to_read], files[last_written]) == 0) - next_to_read++; + { + free ((char *)files[next_to_read]); + next_to_read++; + } else files[++last_written] = files[next_to_read++]; } diff --git a/src/libsysprof-capture/sysprof-capture-reader.h b/src/libsysprof-capture/sysprof-capture-reader.h index 9d10d700..fbf6f143 100644 --- a/src/libsysprof-capture/sysprof-capture-reader.h +++ b/src/libsysprof-capture/sysprof-capture-reader.h @@ -111,6 +111,8 @@ const SysprofCaptureProcess *sysprof_capture_reader_read_process ( SYSPROF_AVAILABLE_IN_ALL const SysprofCaptureSample *sysprof_capture_reader_read_sample (SysprofCaptureReader *self); SYSPROF_AVAILABLE_IN_ALL +const SysprofCaptureTrace *sysprof_capture_reader_read_trace (SysprofCaptureReader *self); +SYSPROF_AVAILABLE_IN_ALL const SysprofCaptureJitmap *sysprof_capture_reader_read_jitmap (SysprofCaptureReader *self); SYSPROF_AVAILABLE_IN_ALL const SysprofCaptureCounterDefine *sysprof_capture_reader_read_counter_define (SysprofCaptureReader *self); diff --git a/src/libsysprof-capture/sysprof-capture-types.h b/src/libsysprof-capture/sysprof-capture-types.h index 20295a2e..9be4a0eb 100644 --- a/src/libsysprof-capture/sysprof-capture-types.h +++ b/src/libsysprof-capture/sysprof-capture-types.h @@ -140,10 +140,11 @@ typedef enum SYSPROF_CAPTURE_FRAME_FILE_CHUNK = 13, SYSPROF_CAPTURE_FRAME_ALLOCATION = 14, SYSPROF_CAPTURE_FRAME_OVERLAY = 15, + SYSPROF_CAPTURE_FRAME_TRACE = 16, } SysprofCaptureFrameType; /* Not part of ABI */ -#define SYSPROF_CAPTURE_FRAME_LAST 16 +#define SYSPROF_CAPTURE_FRAME_LAST 17 SYSPROF_ALIGNED_BEGIN(1) typedef struct @@ -225,6 +226,18 @@ typedef struct } SysprofCaptureSample SYSPROF_ALIGNED_END(1); +SYSPROF_ALIGNED_BEGIN(1) +typedef struct +{ + SysprofCaptureFrame frame; + uint32_t n_addrs : 16; + uint32_t entering : 1; + uint32_t padding1 : 15; + int32_t tid; + SysprofCaptureAddress addrs[0]; +} SysprofCaptureTrace +SYSPROF_ALIGNED_END(1); + SYSPROF_ALIGNED_BEGIN(1) typedef struct { @@ -357,6 +370,7 @@ SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureMap) == 56, "SysprofCaptureMap chan SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureJitmap) == 28, "SysprofCaptureJitmap changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureProcess) == 24, "SysprofCaptureProcess changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureSample) == 32, "SysprofCaptureSample changed size"); +SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureTrace) == 32, "SysprofCaptureTrace changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureFork) == 28, "SysprofCaptureFork changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureExit) == 24, "SysprofCaptureExit changed size"); SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureTimestamp) == 24, "SysprofCaptureTimestamp changed size"); @@ -373,6 +387,7 @@ SYSPROF_STATIC_ASSERT (sizeof (SysprofCaptureOverlay) == 32, "SysprofCaptureOver SYSPROF_STATIC_ASSERT ((offsetof (SysprofCaptureAllocation, addrs) % SYSPROF_CAPTURE_ALIGN) == 0, "SysprofCaptureAllocation.addrs is not aligned"); SYSPROF_STATIC_ASSERT ((offsetof (SysprofCaptureSample, addrs) % SYSPROF_CAPTURE_ALIGN) == 0, "SysprofCaptureSample.addrs is not aligned"); +SYSPROF_STATIC_ASSERT ((offsetof (SysprofCaptureTrace, addrs) % SYSPROF_CAPTURE_ALIGN) == 0, "SysprofCaptureTrace.addrs is not aligned"); static inline int sysprof_capture_address_compare (SysprofCaptureAddress a, diff --git a/src/libsysprof-capture/sysprof-capture-writer-cat.c b/src/libsysprof-capture/sysprof-capture-writer-cat.c index 35de0620..e8de2367 100644 --- a/src/libsysprof-capture/sysprof-capture-writer-cat.c +++ b/src/libsysprof-capture/sysprof-capture-writer-cat.c @@ -416,6 +416,32 @@ sysprof_capture_writer_cat (SysprofCaptureWriter *self, break; } + case SYSPROF_CAPTURE_FRAME_TRACE: + { + const SysprofCaptureTrace *frame; + + if (!(frame = sysprof_capture_reader_read_trace (reader))) + goto panic; + + { + SysprofCaptureAddress addrs[frame->n_addrs]; + + for (unsigned int z = 0; z < frame->n_addrs; z++) + addrs[z] = translate_table_translate (tables, TRANSLATE_ADDR, frame->addrs[z]); + + sysprof_capture_writer_add_trace (self, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + frame->tid, + addrs, + frame->n_addrs, + frame->entering); + } + + break; + } + case SYSPROF_CAPTURE_FRAME_CTRDEF: { const SysprofCaptureCounterDefine *frame; diff --git a/src/libsysprof-capture/sysprof-capture-writer.c b/src/libsysprof-capture/sysprof-capture-writer.c index d05981c2..4b703d3d 100644 --- a/src/libsysprof-capture/sysprof-capture-writer.c +++ b/src/libsysprof-capture/sysprof-capture-writer.c @@ -798,6 +798,42 @@ sysprof_capture_writer_add_sample (SysprofCaptureWriter *self, return true; } +bool +sysprof_capture_writer_add_trace (SysprofCaptureWriter *self, + int64_t time, + int cpu, + int32_t pid, + int32_t tid, + const SysprofCaptureAddress *addrs, + unsigned int n_addrs, + bool entering) +{ + SysprofCaptureTrace *ev; + size_t len; + + assert (self != NULL); + + len = sizeof *ev + (n_addrs * sizeof (SysprofCaptureAddress)); + + ev = (SysprofCaptureTrace *)sysprof_capture_writer_allocate (self, &len); + if (!ev) + return false; + + sysprof_capture_writer_frame_init (&ev->frame, + len, + cpu, + pid, + time, + SYSPROF_CAPTURE_FRAME_SAMPLE); + ev->n_addrs = n_addrs; + ev->tid = tid; + ev->entering = !!entering; + + memcpy (ev->addrs, addrs, (n_addrs * sizeof (SysprofCaptureAddress))); + + return true; +} + bool sysprof_capture_writer_add_fork (SysprofCaptureWriter *self, int64_t time, @@ -1659,7 +1695,7 @@ _sysprof_capture_writer_add_raw (SysprofCaptureWriter *self, return false; assert (fr->len == len); - assert (fr->type < 16); + assert (fr->type < SYSPROF_CAPTURE_FRAME_LAST); memcpy (begin, fr, fr->len); @@ -1668,3 +1704,14 @@ _sysprof_capture_writer_add_raw (SysprofCaptureWriter *self, return true; } + +int +_sysprof_capture_writer_dup_fd (SysprofCaptureWriter *self) +{ + assert (self != NULL); + + if (self->fd == -1) + return -1; + + return dup (self->fd); +} diff --git a/src/libsysprof-capture/sysprof-capture-writer.h b/src/libsysprof-capture/sysprof-capture-writer.h index 4893c226..bde8c174 100644 --- a/src/libsysprof-capture/sysprof-capture-writer.h +++ b/src/libsysprof-capture/sysprof-capture-writer.h @@ -143,6 +143,15 @@ bool sysprof_capture_writer_add_sample (Sy const SysprofCaptureAddress *addrs, unsigned int n_addrs); SYSPROF_AVAILABLE_IN_ALL +bool sysprof_capture_writer_add_trace (SysprofCaptureWriter *self, + int64_t time, + int cpu, + int32_t pid, + int32_t tid, + const SysprofCaptureAddress *addrs, + unsigned int n_addrs, + bool entering); +SYSPROF_AVAILABLE_IN_ALL bool sysprof_capture_writer_add_fork (SysprofCaptureWriter *self, int64_t time, int cpu, @@ -235,5 +244,7 @@ SYSPROF_INTERNAL bool _sysprof_capture_writer_set_time_range (SysprofCaptureWriter *self, int64_t start_time, int64_t end_time) SYSPROF_INTERNAL; +SYSPROF_INTERNAL +int _sysprof_capture_writer_dup_fd (SysprofCaptureWriter *self); SYSPROF_END_DECLS diff --git a/src/libsysprof-capture/sysprof-collector.c b/src/libsysprof-capture/sysprof-collector.c index 803aa02e..c8fad1b8 100644 --- a/src/libsysprof-capture/sysprof-collector.c +++ b/src/libsysprof-capture/sysprof-collector.c @@ -576,6 +576,43 @@ sysprof_collector_sample (SysprofBacktraceFunc backtrace_func, } COLLECTOR_END; } +void +sysprof_collector_trace (SysprofBacktraceFunc backtrace_func, + void *backtrace_data, + bool entering) +{ + COLLECTOR_BEGIN { + SysprofCaptureTrace *ev; + size_t len; + + len = sizeof *ev + (sizeof (SysprofCaptureTrace) * MAX_UNWIND_DEPTH); + + if ((ev = mapped_ring_buffer_allocate (collector->buffer, len))) + { + int n_addrs; + + /* See comment from sysprof_collector_allocate(). */ + if (backtrace_func) + n_addrs = backtrace_func (ev->addrs, MAX_UNWIND_DEPTH, backtrace_data); + else + n_addrs = 0; + + ev->n_addrs = ((n_addrs < 0) ? 0 : (n_addrs > MAX_UNWIND_DEPTH) ? MAX_UNWIND_DEPTH : n_addrs); + ev->frame.len = sizeof *ev + sizeof (SysprofCaptureAddress) * ev->n_addrs; + ev->frame.type = SYSPROF_CAPTURE_FRAME_TRACE; + ev->frame.cpu = _do_getcpu (); + ev->frame.pid = collector->pid; + ev->frame.time = SYSPROF_CAPTURE_CURRENT_TIME; + ev->tid = collector->tid; + ev->entering = !!entering; + ev->padding1 = 0; + + mapped_ring_buffer_advance (collector->buffer, ev->frame.len); + } + + } COLLECTOR_END; +} + void sysprof_collector_mark (int64_t time, int64_t duration, diff --git a/src/libsysprof-capture/sysprof-collector.h b/src/libsysprof-capture/sysprof-collector.h index 6f13a805..c53dc984 100644 --- a/src/libsysprof-capture/sysprof-collector.h +++ b/src/libsysprof-capture/sysprof-collector.h @@ -75,6 +75,10 @@ void sysprof_collector_allocate (SysprofCaptureAddress SYSPROF_AVAILABLE_IN_3_36 void sysprof_collector_sample (SysprofBacktraceFunc backtrace_func, void *backtrace_data); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_collector_trace (SysprofBacktraceFunc backtrace_func, + void *backtrace_data, + bool entering); SYSPROF_AVAILABLE_IN_3_36 void sysprof_collector_mark (int64_t time, int64_t duration, diff --git a/src/tests/allocs-by-size.c b/src/libsysprof-capture/tests/allocs-by-size.c similarity index 98% rename from src/tests/allocs-by-size.c rename to src/libsysprof-capture/tests/allocs-by-size.c index d98e0f6f..ade4eb5b 100644 --- a/src/tests/allocs-by-size.c +++ b/src/libsysprof-capture/tests/allocs-by-size.c @@ -24,8 +24,8 @@ #include #include #include -#include -#include + +#include typedef struct { diff --git a/src/tests/cross-thread-frees.c b/src/libsysprof-capture/tests/cross-thread-frees.c similarity index 98% rename from src/tests/cross-thread-frees.c rename to src/libsysprof-capture/tests/cross-thread-frees.c index f931e61c..0a90a34e 100644 --- a/src/tests/cross-thread-frees.c +++ b/src/libsysprof-capture/tests/cross-thread-frees.c @@ -23,8 +23,8 @@ #include #include #include -#include -#include + +#include typedef struct { diff --git a/src/tests/find-temp-allocs.c b/src/libsysprof-capture/tests/find-temp-allocs.c similarity index 100% rename from src/tests/find-temp-allocs.c rename to src/libsysprof-capture/tests/find-temp-allocs.c diff --git a/src/libsysprof-capture/tests/meson.build b/src/libsysprof-capture/tests/meson.build new file mode 100644 index 00000000..d5d7ddb0 --- /dev/null +++ b/src/libsysprof-capture/tests/meson.build @@ -0,0 +1,40 @@ +libsysprof_capture_test_env = [ + 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), + 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), + 'G_DEBUG=gc-friendly', + 'GSETTINGS_BACKEND=memory', + 'MALLOC_CHECK_=2', + 'NO_AT_BRIDGE=1', +] + +libsysprof_capture_testsuite_c_args = [ + '-DSYSPROF_COMPILATION', + '-DG_ENABLE_DEBUG', + '-UG_DISABLE_ASSERT', + '-UG_DISABLE_CAST_CHECKS', +] + +libsysprof_capture_testsuite = { + 'allocs-by-size' : {'skip': true}, + 'cross-thread-frees' : {'skip': true}, + 'find-temp-allocs' : {'skip': true}, + 'rewrite-pid' : {'skip': true}, + 'test-capture' : {}, + 'test-capture-cursor' : {}, + 'test-mapped-ring-buffer' : {}, +} + +libsysprof_capture_testsuite_deps = [ + dependency('gio-2.0'), + libsysprof_capture_dep, +] + +foreach test, params: libsysprof_capture_testsuite + test_exe = executable(test, '@0@.c'.format(test), + c_args: libsysprof_capture_testsuite_c_args, + dependencies: libsysprof_capture_testsuite_deps, + ) + if not params.get('skip', false) + test(test, test_exe, env: libsysprof_capture_test_env) + endif +endforeach diff --git a/src/tools/rewrite-pid.c b/src/libsysprof-capture/tests/rewrite-pid.c similarity index 100% rename from src/tools/rewrite-pid.c rename to src/libsysprof-capture/tests/rewrite-pid.c diff --git a/src/tests/test-capture-cursor.c b/src/libsysprof-capture/tests/test-capture-cursor.c similarity index 100% rename from src/tests/test-capture-cursor.c rename to src/libsysprof-capture/tests/test-capture-cursor.c diff --git a/src/tests/test-capture.c b/src/libsysprof-capture/tests/test-capture.c similarity index 99% rename from src/tests/test-capture.c rename to src/libsysprof-capture/tests/test-capture.c index c06ad209..a59dcab4 100644 --- a/src/tests/test-capture.c +++ b/src/libsysprof-capture/tests/test-capture.c @@ -732,7 +732,7 @@ test_reader_writer_file (void) g_autofree gchar *data = NULL; g_autofree gchar *testfile = NULL; GByteArray *buf = g_byte_array_new (); - g_autofree const gchar **files = NULL; + const char **files; SysprofCaptureWriter *writer; SysprofCaptureReader *reader; SysprofCaptureFrameType type; @@ -799,6 +799,7 @@ test_reader_writer_file (void) g_assert_nonnull (files); g_assert_cmpstr (files[0], ==, testfile); g_assert_null (files[1]); + free (files); sysprof_capture_reader_reset (reader); new_fd = sysprof_memfd_create ("[sysprof-capture-file]"); diff --git a/src/tests/test-mapped-ring-buffer.c b/src/libsysprof-capture/tests/test-mapped-ring-buffer.c similarity index 97% rename from src/tests/test-mapped-ring-buffer.c rename to src/libsysprof-capture/tests/test-mapped-ring-buffer.c index 707c0dcb..444df156 100644 --- a/src/tests/test-mapped-ring-buffer.c +++ b/src/libsysprof-capture/tests/test-mapped-ring-buffer.c @@ -163,6 +163,9 @@ test_threaded_movements (void) g_thread_join (thread1); g_thread_join (thread2); + + mapped_ring_buffer_unref (writer); + mapped_ring_buffer_unref (reader); } static void @@ -183,6 +186,8 @@ test_readwrite (void) mapped_ring_buffer_advance (ring, sizeof *ptr); } mapped_ring_buffer_drain (ring, drain_count_cb, NULL); + + mapped_ring_buffer_unref (ring); } gint diff --git a/src/libsysprof-ui/css/SysprofDisplay-shared.css b/src/libsysprof-ui/css/SysprofDisplay-shared.css deleted file mode 100644 index a4ca6d57..00000000 --- a/src/libsysprof-ui/css/SysprofDisplay-shared.css +++ /dev/null @@ -1,86 +0,0 @@ -SysprofVisualizer { - background: @content_view_bg; - } -SysprofVisualizer:not(:last-child) { - border-bottom: 1px solid alpha(@borders, 0.3); - } - -SysprofVisualizerGroup { - border-bottom: 1px solid @borders; - } -SysprofVisualizerGroup:last-child { - box-shadow: 0 20px 15px 15px alpha(@borders, 0.3); - } - -SysprofVisualizersFrame box.horizontal.inline-toolbar { - padding: 0; - margin: 0; - border: none; - border-radius: 0; - border-width: 0; - } - -SysprofVisualizersFrame viewport.visualizers { - border-right: 1px solid @borders; - box-shadow: 1px 1px 3px alpha(@borders, 0.5); - } - -SysprofVisualizersFrame scrollbar.horizontal { - color: mix(@theme_fg_color, @theme_selected_bg_color, 0.5); - background: transparent; - } - -SysprofVisualizersFrame scrollbar.horizontal range trough { - background: transparent; - } - -SysprofVisualizersFrame scrollbar.horizontal range trough slider { - margin-left: 1px; - margin-right: 1px; - padding: 6px; - min-height: 14px; - background: alpha(@content_view_bg, 0.2); - border-radius: 3px; - border: 2px solid @theme_selected_bg_color; - box-shadow: inset 0 10px 5px alpha(@content_view_bg,.3), - inset 0 -15px 5px alpha(@content_view_bg,.1), - 0px 2px 4px @borders; - } - -SysprofVisualizerTicks { - color: mix(@theme_fg_color, @borders, 0.5); - background-color: @content_view_bg; - } - -SysprofVisualizersFrame list { - background-color: @theme_bg_color; - } - -SysprofVisualizersFrame list.visualizer-groups row { - padding: 0; - border-bottom: 1px solid @borders; - } -SysprofVisualizersFrame list.visualizer-groups row:not(:selected) { - background-color: @theme_bg_color; - } -SysprofVisualizersFrame list.visualizer-groups row:last-child { - box-shadow: 0 20px 15px 15px alpha(@borders, 0.3); - } - -SysprofVisualizersFrame .left-column .small-button.flat { - border-color: transparent; - min-height: 8px; - min-width: 8px; - } -SysprofVisualizersFrame .left-column .small-button.flat:checked, -SysprofVisualizersFrame .left-column .small-button.flat:hover { - border-color: @borders; - } - -SysprofVisualizersFrame .selection { - border-radius: 3px; - background-color: alpha(@theme_selected_bg_color, 0.35); - box-shadow: inset 0 10px 5px alpha(@content_view_bg,.3), - inset 0 -15px 5px alpha(@content_view_bg,.1), - inset 0 0 1px 1px @theme_selected_bg_color; - } diff --git a/src/libsysprof-ui/css/SysprofEnvironEditor-shared.css b/src/libsysprof-ui/css/SysprofEnvironEditor-shared.css deleted file mode 100644 index e202b29b..00000000 --- a/src/libsysprof-ui/css/SysprofEnvironEditor-shared.css +++ /dev/null @@ -1,13 +0,0 @@ -list.environ-editor row button.flat:last-child { - min-height: 16px; - min-width: 16px; - padding: 0; - margin: 3px; -} -list.environ-editor row button.flat:not(:hover) { - border-color: transparent; -} -list.environ-editor row button.flat image { - padding: 3px; - margin: 0; -} diff --git a/src/libsysprof-ui/css/SysprofProfilerAssistant-shared.css b/src/libsysprof-ui/css/SysprofProfilerAssistant-shared.css deleted file mode 100644 index f0acc7e4..00000000 --- a/src/libsysprof-ui/css/SysprofProfilerAssistant-shared.css +++ /dev/null @@ -1,7 +0,0 @@ -sysprofaidicon image.right.top { - border-radius: 9999px; - background-color: @theme_selected_bg_color; - color: @theme_selected_fg_color; - padding: 2px; - margin: 0px; -} diff --git a/src/libsysprof-ui/egg-handle-private.h b/src/libsysprof-ui/egg-handle-private.h deleted file mode 100644 index 9c004aa0..00000000 --- a/src/libsysprof-ui/egg-handle-private.h +++ /dev/null @@ -1,35 +0,0 @@ -/* egg-handle.h - * - * Copyright 2021 Christian Hergert - * - * This file is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define EGG_TYPE_HANDLE (egg_handle_get_type()) - -G_DECLARE_FINAL_TYPE (EggHandle, egg_handle, EGG, HANDLE, GtkWidget) - -GtkWidget *egg_handle_new (GtkPositionType position); -void egg_handle_set_position (EggHandle *self, - GtkPositionType position); - -G_END_DECLS diff --git a/src/libsysprof-ui/egg-handle.c b/src/libsysprof-ui/egg-handle.c deleted file mode 100644 index 5872abb3..00000000 --- a/src/libsysprof-ui/egg-handle.c +++ /dev/null @@ -1,145 +0,0 @@ -/* egg-handle.c - * - * Copyright 2021 Christian Hergert - * - * This file is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -#include "config.h" - -#include "egg-handle-private.h" - -#define EXTRA_SIZE 8 - -struct _EggHandle -{ - GtkWidget parent_instance; - GtkWidget *separator; - GtkPositionType position : 3; -}; - -G_DEFINE_TYPE (EggHandle, egg_handle, GTK_TYPE_WIDGET) - -static gboolean -egg_handle_contains (GtkWidget *widget, - double x, - double y) -{ - EggHandle *self = (EggHandle *)widget; - graphene_rect_t area; - - g_assert (EGG_IS_HANDLE (self)); - - if (!gtk_widget_compute_bounds (GTK_WIDGET (self->separator), - GTK_WIDGET (self), - &area)) - return FALSE; - - switch (self->position) - { - case GTK_POS_LEFT: - area.origin.x -= EXTRA_SIZE; - area.size.width = EXTRA_SIZE; - break; - - case GTK_POS_RIGHT: - area.size.width = EXTRA_SIZE; - break; - - case GTK_POS_TOP: - area.origin.y -= EXTRA_SIZE; - area.size.height = EXTRA_SIZE; - break; - - case GTK_POS_BOTTOM: - area.size.height = EXTRA_SIZE; - break; - - default: - g_assert_not_reached (); - break; - } - - return graphene_rect_contains_point (&area, &GRAPHENE_POINT_INIT (x, y)); -} - -static void -egg_handle_dispose (GObject *object) -{ - EggHandle *self = (EggHandle *)object; - - g_clear_pointer (&self->separator, gtk_widget_unparent); - - G_OBJECT_CLASS (egg_handle_parent_class)->dispose (object); -} - -static void -egg_handle_class_init (EggHandleClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = egg_handle_dispose; - - widget_class->contains = egg_handle_contains; - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); -} - -static void -egg_handle_init (EggHandle *self) -{ - self->separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); - gtk_widget_set_parent (GTK_WIDGET (self->separator), GTK_WIDGET (self)); -} - -void -egg_handle_set_position (EggHandle *self, - GtkPositionType position) -{ - g_return_if_fail (EGG_IS_HANDLE (self)); - - self->position = position; - - switch (position) - { - case GTK_POS_LEFT: - case GTK_POS_RIGHT: - gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "col-resize"); - gtk_orientable_set_orientation (GTK_ORIENTABLE (self->separator), GTK_ORIENTATION_VERTICAL); - break; - - case GTK_POS_TOP: - case GTK_POS_BOTTOM: - gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "row-resize"); - gtk_orientable_set_orientation (GTK_ORIENTABLE (self->separator), GTK_ORIENTATION_HORIZONTAL); - break; - - default: - g_assert_not_reached (); - } -} - -GtkWidget * -egg_handle_new (GtkPositionType position) -{ - EggHandle *self; - - self = g_object_new (EGG_TYPE_HANDLE, NULL); - egg_handle_set_position (self, position); - - return GTK_WIDGET (self); -} diff --git a/src/libsysprof-ui/egg-paned-private.h b/src/libsysprof-ui/egg-paned-private.h deleted file mode 100644 index b673c1a4..00000000 --- a/src/libsysprof-ui/egg-paned-private.h +++ /dev/null @@ -1,48 +0,0 @@ -/* egg-paned.h - * - * Copyright 2021 Christian Hergert - * - * This file is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define EGG_TYPE_PANED (egg_paned_get_type()) - -G_DECLARE_FINAL_TYPE (EggPaned, egg_paned, EGG, PANED, GtkWidget) - -GtkWidget *egg_paned_new (void); -void egg_paned_append (EggPaned *self, - GtkWidget *child); -void egg_paned_prepend (EggPaned *self, - GtkWidget *child); -void egg_paned_insert (EggPaned *self, - int position, - GtkWidget *child); -void egg_paned_insert_after (EggPaned *self, - GtkWidget *child, - GtkWidget *sibling); -void egg_paned_remove (EggPaned *self, - GtkWidget *child); -guint egg_paned_get_n_children (EggPaned *self); -GtkWidget *egg_paned_get_nth_child (EggPaned *self, - guint nth); - -G_END_DECLS diff --git a/src/libsysprof-ui/egg-paned.c b/src/libsysprof-ui/egg-paned.c deleted file mode 100644 index 25b0d82c..00000000 --- a/src/libsysprof-ui/egg-paned.c +++ /dev/null @@ -1,593 +0,0 @@ -/* egg-paned.c - * - * Copyright 2021 Christian Hergert - * - * This file is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "egg-paned-private.h" -#include "egg-resizer-private.h" - -struct _EggPaned -{ - GtkWidget parent_instance; - GtkOrientation orientation; -}; - -static void buildable_iface_init (GtkBuildableIface *iface); - -G_DEFINE_TYPE_WITH_CODE (EggPaned, egg_paned, GTK_TYPE_WIDGET, - G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init) - G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) - -enum { - PROP_0, - N_PROPS, - - PROP_ORIENTATION, -}; - -static void -update_orientation (GtkWidget *widget, - GtkOrientation orientation) -{ - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - gtk_widget_remove_css_class (widget, "vertical"); - gtk_widget_add_css_class (widget, "horizontal"); - } - else - { - gtk_widget_remove_css_class (widget, "horizontal"); - gtk_widget_add_css_class (widget, "vertical"); - } - - gtk_accessible_update_property (GTK_ACCESSIBLE (widget), - GTK_ACCESSIBLE_PROPERTY_ORIENTATION, orientation, - -1); -} - -/** - * egg_paned_new: - * - * Create a new #EggPaned. - * - * Returns: (transfer full): a newly created #EggPaned - */ -GtkWidget * -egg_paned_new (void) -{ - return g_object_new (EGG_TYPE_PANED, NULL); -} - -static void -egg_paned_set_orientation (EggPaned *self, - GtkOrientation orientation) -{ - GtkPositionType pos; - - g_assert (EGG_IS_PANED (self)); - g_assert (orientation == GTK_ORIENTATION_HORIZONTAL || - orientation == GTK_ORIENTATION_VERTICAL); - - if (self->orientation == orientation) - return; - - self->orientation = orientation; - - if (self->orientation == GTK_ORIENTATION_HORIZONTAL) - pos = GTK_POS_LEFT; - else - pos = GTK_POS_TOP; - - for (GtkWidget *child = gtk_widget_get_last_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_prev_sibling (child)) - { - g_assert (EGG_IS_RESIZER (child)); - - egg_resizer_set_position (EGG_RESIZER (child), pos); - } - - update_orientation (GTK_WIDGET (self), self->orientation); - gtk_widget_queue_resize (GTK_WIDGET (self)); - g_object_notify (G_OBJECT (self), "orientation"); -} - -static void -egg_paned_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - EggPaned *self = (EggPaned *)widget; - - g_assert (EGG_IS_PANED (self)); - - *minimum = 0; - *natural = 0; - *minimum_baseline = -1; - *natural_baseline = -1; - - for (GtkWidget *child = gtk_widget_get_first_child (widget); - child != NULL; - child = gtk_widget_get_next_sibling (child)) - { - int child_min, child_nat; - - gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL); - - if (orientation == self->orientation) - { - *minimum += child_min; - *natural += child_nat; - } - else - { - *minimum = MAX (*minimum, child_min); - *natural = MAX (*natural, child_nat); - } - } -} - -typedef struct -{ - GtkWidget *widget; - GtkRequisition min_request; - GtkRequisition nat_request; - GtkAllocation alloc; -} ChildAllocation; - -static void -egg_paned_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - EggPaned *self = (EggPaned *)widget; - ChildAllocation *allocs; - ChildAllocation *last_alloc = NULL; - GtkOrientation orientation; - guint n_children = 0; - guint n_expand = 0; - guint i; - int extra_width = width; - int extra_height = height; - int expand_width; - int expand_height; - int x, y; - - g_assert (EGG_IS_PANED (self)); - - GTK_WIDGET_CLASS (egg_paned_parent_class)->size_allocate (widget, width, height, baseline); - - n_children = egg_paned_get_n_children (self); - - if (n_children == 1) - { - GtkWidget *child = gtk_widget_get_first_child (widget); - GtkAllocation alloc = { 0, 0, width, height }; - - if (gtk_widget_get_visible (child)) - { - gtk_widget_size_allocate (child, &alloc, -1); - return; - } - } - - orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self)); - allocs = g_newa (ChildAllocation, n_children); - memset (allocs, 0, sizeof *allocs * n_children); - - /* Give min size to each of the children */ - i = 0; - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_next_sibling (child), i++) - { - ChildAllocation *child_alloc = &allocs[i]; - - if (!gtk_widget_get_visible (child)) - continue; - - gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, height, - &child_alloc->min_request.width, - &child_alloc->nat_request.width, - NULL, NULL); - gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, width, - &child_alloc->min_request.height, - &child_alloc->nat_request.height, - NULL, NULL); - - child_alloc->alloc.width = child_alloc->min_request.width; - child_alloc->alloc.height = child_alloc->min_request.height; - - n_expand += gtk_widget_compute_expand (child, orientation); - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - extra_width -= child_alloc->alloc.width; - child_alloc->alloc.height = height; - } - else - { - extra_height -= child_alloc->alloc.height; - child_alloc->alloc.width = width; - } - } - - /* Now try to distribute extra space for natural size */ - i = 0; - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_next_sibling (child), i++) - { - ChildAllocation *child_alloc = &allocs[i]; - - if (!gtk_widget_get_visible (child)) - continue; - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - int taken = MIN (extra_width, child_alloc->nat_request.width - child_alloc->alloc.width); - - if (taken > 0) - { - child_alloc->alloc.width += taken; - extra_width -= taken; - } - } - else - { - int taken = MIN (extra_height, child_alloc->nat_request.height - child_alloc->alloc.height); - - if (taken > 0) - { - child_alloc->alloc.height += taken; - extra_height -= taken; - } - } - - last_alloc = child_alloc; - } - - /* Now give extra space for those that expand */ - expand_width = n_expand ? extra_width / n_expand : 0; - expand_height = n_expand ? extra_height / n_expand : 0; - i = n_children; - for (GtkWidget *child = gtk_widget_get_last_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_prev_sibling (child), i--) - { - ChildAllocation *child_alloc = &allocs[i-1]; - - if (!gtk_widget_get_visible (child)) - continue; - - if (!gtk_widget_compute_expand (child, orientation)) - continue; - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - child_alloc->alloc.width += expand_width; - extra_width -= expand_width; - } - else - { - child_alloc->alloc.height += expand_height; - extra_height -= expand_height; - } - } - - /* Give any leftover to the last visible child */ - if (last_alloc) - { - if (orientation == GTK_ORIENTATION_HORIZONTAL) - last_alloc->alloc.width += extra_width; - else - last_alloc->alloc.height += extra_height; - } - - i = 0; - x = 0; - y = 0; - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_next_sibling (child), i++) - { - ChildAllocation *child_alloc = &allocs[i]; - - child_alloc->alloc.x = x; - child_alloc->alloc.y = y; - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - x += child_alloc->alloc.width; - else - y += child_alloc->alloc.height; - - gtk_widget_size_allocate (child, &child_alloc->alloc, -1); - } -} - -static void -egg_paned_dispose (GObject *object) -{ - EggPaned *self = (EggPaned *)object; - GtkWidget *child; - - child = gtk_widget_get_first_child (GTK_WIDGET (self)); - while (child) - { - GtkWidget *next = gtk_widget_get_next_sibling (child); - gtk_widget_unparent (child); - child = next; - } - - G_OBJECT_CLASS (egg_paned_parent_class)->dispose (object); -} - -static void -egg_paned_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - EggPaned *self = EGG_PANED (object); - - switch (prop_id) - { - case PROP_ORIENTATION: - g_value_set_enum (value, self->orientation); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -egg_paned_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - EggPaned *self = EGG_PANED (object); - - switch (prop_id) - { - case PROP_ORIENTATION: - egg_paned_set_orientation (self, g_value_get_enum (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -egg_paned_class_init (EggPanedClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = egg_paned_dispose; - object_class->get_property = egg_paned_get_property; - object_class->set_property = egg_paned_set_property; - - widget_class->measure = egg_paned_measure; - widget_class->size_allocate = egg_paned_size_allocate; - - g_object_class_override_property (object_class, PROP_ORIENTATION, "orientation"); - - gtk_widget_class_set_css_name (widget_class, "eggpaned"); -} - -static void -egg_paned_init (EggPaned *self) -{ - self->orientation = GTK_ORIENTATION_HORIZONTAL; - - update_orientation (GTK_WIDGET (self), self->orientation); -} - -static void -egg_paned_update_handles (EggPaned *self) -{ - GtkWidget *child; - - g_assert (EGG_IS_PANED (self)); - - for (child = gtk_widget_get_first_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_next_sibling (child)) - { - GtkWidget *handle; - - g_assert (EGG_IS_RESIZER (child)); - - if ((handle = egg_resizer_get_handle (EGG_RESIZER (child)))) - gtk_widget_show (handle); - } - - if ((child = gtk_widget_get_last_child (GTK_WIDGET (self)))) - { - GtkWidget *handle = egg_resizer_get_handle (EGG_RESIZER (child)); - gtk_widget_hide (handle); - } -} - -void -egg_paned_remove (EggPaned *self, - GtkWidget *child) -{ - GtkWidget *resizer; - - g_return_if_fail (EGG_IS_PANED (self)); - g_return_if_fail (GTK_IS_WIDGET (child)); - - resizer = gtk_widget_get_ancestor (child, EGG_TYPE_RESIZER); - g_return_if_fail (resizer != NULL && - gtk_widget_get_parent (resizer) == GTK_WIDGET (self)); - gtk_widget_unparent (resizer); - egg_paned_update_handles (self); - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -void -egg_paned_insert (EggPaned *self, - int position, - GtkWidget *child) -{ - GtkPositionType pos; - GtkWidget *resizer; - - g_return_if_fail (EGG_IS_PANED (self)); - g_return_if_fail (GTK_IS_WIDGET (child)); - g_return_if_fail (gtk_widget_get_parent (child) == NULL); - - if (self->orientation == GTK_ORIENTATION_HORIZONTAL) - pos = GTK_POS_LEFT; - else - pos = GTK_POS_TOP; - - resizer = egg_resizer_new (pos); - egg_resizer_set_child (EGG_RESIZER (resizer), child); - - if (position < 0) - gtk_widget_insert_before (GTK_WIDGET (resizer), GTK_WIDGET (self), NULL); - else if (position == 0) - gtk_widget_insert_after (GTK_WIDGET (resizer), GTK_WIDGET (self), NULL); - else - { - GtkWidget *sibling = gtk_widget_get_first_child (GTK_WIDGET (self)); - - for (int i = position; i > 0 && sibling != NULL; i--) - sibling = gtk_widget_get_next_sibling (sibling); - - gtk_widget_insert_before (GTK_WIDGET (resizer), GTK_WIDGET (self), sibling); - } - - egg_paned_update_handles (self); - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -void -egg_paned_append (EggPaned *self, - GtkWidget *child) -{ - egg_paned_insert (self, -1, child); -} - -void -egg_paned_prepend (EggPaned *self, - GtkWidget *child) -{ - egg_paned_insert (self, 0, child); -} - -void -egg_paned_insert_after (EggPaned *self, - GtkWidget *child, - GtkWidget *sibling) -{ - int position = 0; - - g_return_if_fail (EGG_IS_PANED (self)); - g_return_if_fail (GTK_IS_WIDGET (child)); - g_return_if_fail (!sibling || GTK_IS_WIDGET (sibling)); - - if (sibling == NULL) - { - egg_paned_prepend (self, child); - return; - } - - /* TODO: We should reverse insert() to call this */ - - for (GtkWidget *ancestor = gtk_widget_get_first_child (GTK_WIDGET (self)); - ancestor != NULL; - ancestor = gtk_widget_get_next_sibling (ancestor)) - { - position++; - - if (sibling == ancestor || gtk_widget_is_ancestor (sibling, ancestor)) - break; - } - - egg_paned_insert (self, position, child); -} - -guint -egg_paned_get_n_children (EggPaned *self) -{ - guint count = 0; - - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_next_sibling (child)) - count++; - - return count; -} - -GtkWidget * -egg_paned_get_nth_child (EggPaned *self, - guint nth) -{ - g_return_val_if_fail (EGG_IS_PANED (self), NULL); - - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); - child != NULL; - child = gtk_widget_get_next_sibling (child)) - { - g_assert (EGG_IS_RESIZER (child)); - - if (nth == 0) - return egg_resizer_get_child (EGG_RESIZER (child)); - - nth--; - } - - return NULL; -} - -static void -egg_paned_add_child (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *type) -{ - if (GTK_IS_WIDGET (child)) - egg_paned_append (EGG_PANED (buildable), GTK_WIDGET (child)); - else - g_warning ("Cannot add child of type %s to %s", - G_OBJECT_TYPE_NAME (child), - G_OBJECT_TYPE_NAME (buildable)); -} - -static void -buildable_iface_init (GtkBuildableIface *iface) -{ - iface->add_child = egg_paned_add_child; -} diff --git a/src/libsysprof-ui/egg-resizer-private.h b/src/libsysprof-ui/egg-resizer-private.h deleted file mode 100644 index 58db56c3..00000000 --- a/src/libsysprof-ui/egg-resizer-private.h +++ /dev/null @@ -1,40 +0,0 @@ -/* egg-resizer.h - * - * Copyright 2021 Christian Hergert - * - * This file is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define EGG_TYPE_RESIZER (egg_resizer_get_type()) - -G_DECLARE_FINAL_TYPE (EggResizer, egg_resizer, EGG, RESIZER, GtkWidget) - -GtkWidget *egg_resizer_new (GtkPositionType position); -GtkPositionType egg_resizer_get_position (EggResizer *self); -void egg_resizer_set_position (EggResizer *self, - GtkPositionType position); -GtkWidget *egg_resizer_get_child (EggResizer *self); -void egg_resizer_set_child (EggResizer *self, - GtkWidget *child); -GtkWidget *egg_resizer_get_handle (EggResizer *self); - -G_END_DECLS diff --git a/src/libsysprof-ui/egg-resizer.c b/src/libsysprof-ui/egg-resizer.c deleted file mode 100644 index c2b427bc..00000000 --- a/src/libsysprof-ui/egg-resizer.c +++ /dev/null @@ -1,495 +0,0 @@ -/* egg-resizer.c - * - * Copyright 2021 Christian Hergert - * - * This file is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * This file is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - * - * SPDX-License-Identifier: LGPL-3.0-or-later - */ - -#include "config.h" - -#include "egg-handle-private.h" -#include "egg-resizer-private.h" - -#define HANDLE_SIZE 8 - -struct _EggResizer -{ - GtkWidget parent_instance; - - EggHandle *handle; - GtkWidget *child; - - double drag_orig_size; - double drag_position; - - GtkPositionType position : 3; -}; - -G_DEFINE_TYPE (EggResizer, egg_resizer, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_CHILD, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -egg_resizer_drag_begin_cb (EggResizer *self, - double start_x, - double start_y, - GtkGestureDrag *drag) -{ - GtkAllocation child_alloc; - GtkAllocation handle_alloc; - - g_assert (EGG_IS_RESIZER (self)); - g_assert (GTK_IS_GESTURE_DRAG (drag)); - - if (self->child == NULL) - return; - - switch (self->position) - { - case GTK_POS_LEFT: - if (start_x > gtk_widget_get_width (GTK_WIDGET (self)) - HANDLE_SIZE) - goto start_drag; - break; - - case GTK_POS_RIGHT: - if (start_x <= HANDLE_SIZE) - goto start_drag; - break; - - case GTK_POS_TOP: - if (start_y > gtk_widget_get_height (GTK_WIDGET (self)) - HANDLE_SIZE) - goto start_drag; - break; - - case GTK_POS_BOTTOM: - if (start_y <= HANDLE_SIZE) - goto start_drag; - break; - - default: - g_assert_not_reached (); - break; - } - - gtk_gesture_set_state (GTK_GESTURE (drag), - GTK_EVENT_SEQUENCE_DENIED); - - return; - -start_drag: - - gtk_widget_get_allocation (self->child, &child_alloc); - gtk_widget_get_allocation (GTK_WIDGET (self->handle), &handle_alloc); - - if (self->position == GTK_POS_LEFT || - self->position == GTK_POS_RIGHT) - { - self->drag_orig_size = child_alloc.width + handle_alloc.width; - gtk_widget_set_hexpand (self->child, FALSE); - } - else - { - self->drag_orig_size = child_alloc.height + handle_alloc.height; - gtk_widget_set_vexpand (self->child, FALSE); - } - - self->drag_position = self->drag_orig_size; - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -egg_resizer_drag_update_cb (EggResizer *self, - double offset_x, - double offset_y, - GtkGestureDrag *drag) -{ - g_assert (EGG_IS_RESIZER (self)); - g_assert (GTK_IS_GESTURE_DRAG (drag)); - - if (self->position == GTK_POS_LEFT) - self->drag_position = self->drag_orig_size + offset_x; - else if (self->position == GTK_POS_RIGHT) - self->drag_position = gtk_widget_get_width (GTK_WIDGET (self)) - offset_x; - else if (self->position == GTK_POS_TOP) - self->drag_position = self->drag_orig_size + offset_y; - else if (self->position == GTK_POS_BOTTOM) - self->drag_position = gtk_widget_get_height (GTK_WIDGET (self)) - offset_y; - - gtk_widget_queue_resize (GTK_WIDGET (self)); -} - -static void -egg_resizer_drag_end_cb (EggResizer *self, - double offset_x, - double offset_y, - GtkGestureDrag *drag) -{ - g_assert (EGG_IS_RESIZER (self)); - g_assert (GTK_IS_GESTURE_DRAG (drag)); -} - -GtkWidget * -egg_resizer_new (GtkPositionType position) -{ - EggResizer *self; - - self = g_object_new (EGG_TYPE_RESIZER, NULL); - self->position = position; - self->handle = EGG_HANDLE (egg_handle_new (position)); - gtk_widget_set_parent (GTK_WIDGET (self->handle), GTK_WIDGET (self)); - - return GTK_WIDGET (self); -} - -static void -egg_resizer_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - EggResizer *self = (EggResizer *)widget; - - g_assert (EGG_IS_RESIZER (self)); - - *minimum = 0; - *natural = 0; - *minimum_baseline = -1; - *natural_baseline = -1; - - if (self->child != NULL) - gtk_widget_measure (self->child, - orientation, - for_size, - minimum, natural, - NULL, NULL); - - if ((orientation == GTK_ORIENTATION_HORIZONTAL && - (self->position == GTK_POS_LEFT || - self->position == GTK_POS_RIGHT)) || - (orientation == GTK_ORIENTATION_VERTICAL && - (self->position == GTK_POS_TOP || - self->position == GTK_POS_BOTTOM))) - { - int handle_min, handle_nat; - - if (self->drag_position != 0) - { - if (self->drag_position > *minimum) - *natural = self->drag_position; - else if (self->drag_position < *minimum) - *natural = *minimum; - } - - if (gtk_widget_get_visible (GTK_WIDGET (self->handle))) - { - gtk_widget_measure (GTK_WIDGET (self->handle), - orientation, for_size, - &handle_min, &handle_nat, - NULL, NULL); - - *minimum += handle_min; - *natural += handle_nat; - } - } -} - -static void -egg_resizer_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - EggResizer *self = (EggResizer *)widget; - GtkOrientation orientation; - GtkAllocation child_alloc; - GtkAllocation handle_alloc; - int handle_min = 0, handle_nat = 0; - - g_assert (EGG_IS_RESIZER (self)); - - if (self->position == GTK_POS_LEFT || - self->position == GTK_POS_RIGHT) - orientation = GTK_ORIENTATION_HORIZONTAL; - else - orientation = GTK_ORIENTATION_VERTICAL; - - if (gtk_widget_get_visible (GTK_WIDGET (self->handle))) - gtk_widget_measure (GTK_WIDGET (self->handle), - orientation, - -1, - &handle_min, &handle_nat, - NULL, NULL); - - switch (self->position) - { - case GTK_POS_LEFT: - handle_alloc.x = width - handle_min; - handle_alloc.width = handle_min; - handle_alloc.y = 0; - handle_alloc.height = height; - child_alloc.x = 0; - child_alloc.y = 0; - child_alloc.width = width - handle_min; - child_alloc.height = height; - break; - - case GTK_POS_RIGHT: - handle_alloc.x = 0; - handle_alloc.width = handle_min; - handle_alloc.y = 0; - handle_alloc.height = height; - child_alloc.x = handle_min; - child_alloc.y = 0; - child_alloc.width = width - handle_min; - child_alloc.height = height; - break; - - case GTK_POS_TOP: - handle_alloc.x = 0; - handle_alloc.width = width; - handle_alloc.y = height - handle_min; - handle_alloc.height = handle_min; - child_alloc.x = 0; - child_alloc.y = 0; - child_alloc.width = width; - child_alloc.height = height - handle_min; - break; - - case GTK_POS_BOTTOM: - handle_alloc.x = 0; - handle_alloc.width = width; - handle_alloc.y = 0; - handle_alloc.height = handle_min; - child_alloc.x = 0; - child_alloc.y = handle_min; - child_alloc.width = width; - child_alloc.height = height - handle_min; - break; - - default: - g_assert_not_reached (); - } - - if (gtk_widget_get_mapped (GTK_WIDGET (self->handle))) - gtk_widget_size_allocate (GTK_WIDGET (self->handle), &handle_alloc, -1); - - if (self->child != NULL && - gtk_widget_get_mapped (self->child)) - gtk_widget_size_allocate (self->child, &child_alloc, -1); -} - -static void -egg_resizer_compute_expand (GtkWidget *widget, - gboolean *hexpand, - gboolean *vexpand) -{ - EggResizer *self = EGG_RESIZER (widget); - - if (self->child != NULL) - { - *hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL); - *vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL); - } - else - { - *hexpand = FALSE; - *vexpand = FALSE; - } -} - -static void -egg_resizer_dispose (GObject *object) -{ - EggResizer *self = (EggResizer *)object; - - if (self->handle) - gtk_widget_unparent (GTK_WIDGET (self->handle)); - self->handle = NULL; - - if (self->child) - gtk_widget_unparent (self->child); - self->child = NULL; - - G_OBJECT_CLASS (egg_resizer_parent_class)->dispose (object); -} - -static void -egg_resizer_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - EggResizer *self = EGG_RESIZER (object); - - switch (prop_id) - { - case PROP_CHILD: - g_value_set_object (value, egg_resizer_get_child (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -egg_resizer_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - EggResizer *self = EGG_RESIZER (object); - - switch (prop_id) - { - case PROP_CHILD: - egg_resizer_set_child (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -egg_resizer_class_init (EggResizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = egg_resizer_dispose; - object_class->get_property = egg_resizer_get_property; - object_class->set_property = egg_resizer_set_property; - - widget_class->compute_expand = egg_resizer_compute_expand; - widget_class->measure = egg_resizer_measure; - widget_class->size_allocate = egg_resizer_size_allocate; - - properties [PROP_CHILD] = - g_param_spec_object ("child", - "Child", - "Child", - GTK_TYPE_WIDGET, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_css_name (widget_class, "eggresizer"); -} - -static void -egg_resizer_init (EggResizer *self) -{ - GtkGesture *gesture; - - gesture = gtk_gesture_drag_new (); - gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE); - g_signal_connect_object (gesture, - "drag-begin", - G_CALLBACK (egg_resizer_drag_begin_cb), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (gesture, - "drag-update", - G_CALLBACK (egg_resizer_drag_update_cb), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (gesture, - "drag-end", - G_CALLBACK (egg_resizer_drag_end_cb), - self, - G_CONNECT_SWAPPED); - gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); -} - -/** - * egg_resizer_get_child: - * @self: a #EggResizer - * - * Gets the child widget of the resizer. - * - * Returns: (transfer none) (nullable): A #GtkWidget or %NULL - */ -GtkWidget * -egg_resizer_get_child (EggResizer *self) -{ - g_return_val_if_fail (EGG_IS_RESIZER (self), NULL); - - return self->child; -} - -void -egg_resizer_set_child (EggResizer *self, - GtkWidget *child) -{ - g_return_if_fail (EGG_IS_RESIZER (self)); - g_return_if_fail (!child || GTK_IS_WIDGET (child)); - - if (child == self->child) - return; - - g_clear_pointer (&self->child, gtk_widget_unparent); - - self->child = child; - - if (self->child != NULL) - gtk_widget_insert_before (self->child, - GTK_WIDGET (self), - GTK_WIDGET (self->handle)); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILD]); -} - -GtkPositionType -egg_resizer_get_position (EggResizer *self) -{ - g_return_val_if_fail (EGG_IS_RESIZER (self), 0); - - return self->position; -} - -void -egg_resizer_set_position (EggResizer *self, - GtkPositionType position) -{ - g_return_if_fail (EGG_IS_RESIZER (self)); - - if (position != self->position) - { - self->position = position; - - egg_handle_set_position (self->handle, position); - gtk_widget_queue_resize (GTK_WIDGET (self)); - } -} - -GtkWidget * -egg_resizer_get_handle (EggResizer *self) -{ - g_return_val_if_fail (EGG_IS_RESIZER (self), NULL); - - return GTK_WIDGET (self->handle); -} diff --git a/src/libsysprof-ui/libsysprof-ui.gresource.xml b/src/libsysprof-ui/libsysprof-ui.gresource.xml deleted file mode 100644 index e5aa0593..00000000 --- a/src/libsysprof-ui/libsysprof-ui.gresource.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - css/SysprofEnvironEditor-shared.css - css/SysprofDisplay-shared.css - css/SysprofProfilerAssistant-shared.css - - - ../../data/icons/org.gnome.Sysprof.svg - ../../data/icons/symbolic/apps/org.gnome.Sysprof-symbolic.svg - ../../data/icons/symbolic/apps/org.gnome.Sysprof-symbolic.svg - - - - sysprof-aid-icon.ui - sysprof-callgraph-page.ui - sysprof-details-page.ui - sysprof-display.ui - sysprof-environ-editor-row.ui - sysprof-failed-state-view.ui - sysprof-logs-page.ui - sysprof-marks-page.ui - sysprof-memprof-page.ui - sysprof-process-model-row.ui - sysprof-profiler-assistant.ui - sysprof-recording-state-view.ui - sysprof-tab.ui - sysprof-visualizers-frame.ui - - diff --git a/src/libsysprof-ui/meson.build b/src/libsysprof-ui/meson.build deleted file mode 100644 index 0017eedb..00000000 --- a/src/libsysprof-ui/meson.build +++ /dev/null @@ -1,136 +0,0 @@ -libsysprof_ui_public_sources = [ - 'sysprof-check.c', - 'sysprof-display.c', - 'sysprof-model-filter.c', - 'sysprof-notebook.c', - 'sysprof-page.c', - 'sysprof-process-model-row.c', - 'sysprof-visualizer.c', - 'sysprof-visualizer-group.c', - 'sysprof-zoom-manager.c', -] - -libsysprof_ui_private_sources = [ - 'egg-handle.c', - 'egg-paned.c', - 'egg-resizer.c', - - 'pointcache.c', - 'rectangles.c', - 'sysprof-aid.c', - 'sysprof-aid-icon.c', - 'sysprof-battery-aid.c', - 'sysprof-cairo.c', - 'sysprof-callgraph-aid.c', - 'sysprof-callgraph-page.c', - 'sysprof-cell-renderer-duration.c', - 'sysprof-cell-renderer-percent.c', - 'sysprof-cell-renderer-progress.c', - 'sysprof-color-cycle.c', - 'sysprof-counters-aid.c', - 'sysprof-cpu-aid.c', - 'sysprof-depth-visualizer.c', - 'sysprof-details-page.c', - 'sysprof-diskstat-aid.c', - 'sysprof-display.c', - 'sysprof-duplex-visualizer.c', - 'sysprof-environ.c', - 'sysprof-environ-editor.c', - 'sysprof-environ-editor-row.c', - 'sysprof-environ-variable.c', - 'sysprof-failed-state-view.c', - 'sysprof-line-visualizer.c', - 'sysprof-log-model.c', - 'sysprof-logs-aid.c', - 'sysprof-logs-page.c', - 'sysprof-mark-detail.c', - 'sysprof-marks-aid.c', - 'sysprof-marks-model.c', - 'sysprof-marks-page.c', - 'sysprof-mark-visualizer.c', - 'sysprof-memory-aid.c', - 'sysprof-memprof-aid.c', - 'sysprof-memprof-page.c', - 'sysprof-memprof-visualizer.c', - 'sysprof-netdev-aid.c', - 'sysprof-procs-visualizer.c', - 'sysprof-profiler-assistant.c', - 'sysprof-proxy-aid.c', - 'sysprof-rapl-aid.c', - 'sysprof-recording-state-view.c', - 'sysprof-scrollmap.c', - 'sysprof-tab.c', - 'sysprof-theme-manager.c', - 'sysprof-time-label.c', - 'sysprof-time-visualizer.c', - 'sysprof-visualizer-group-header.c', - 'sysprof-visualizers-frame.c', - 'sysprof-visualizer-ticks.c', - '../stackstash.c', -] - -libsysprof_ui_public_headers = [ - 'sysprof-check.h', - 'sysprof-display.h', - 'sysprof-model-filter.h', - 'sysprof-notebook.h', - 'sysprof-page.h', - 'sysprof-process-model-row.h', - 'sysprof-visualizer.h', - 'sysprof-visualizer-group.h', - 'sysprof-zoom-manager.h', - 'sysprof-ui.h', -] - -libsysprof_ui_resources = gnome.compile_resources( - 'libsysprof-ui-resources', - 'libsysprof-ui.gresource.xml', - c_name: 'lisysprof_ui', -) - -# Subset of dependencies used in generating the pkg-config file -libsysprof_ui_pkg_deps = [ - dependency('gio-2.0', version: glib_req_version), - dependency('gtk4', version: gtk_req_version), - dependency('libadwaita-1'), -] - -libsysprof_ui_deps = libsysprof_ui_pkg_deps + [ - libsysprof_dep, -] - -# Meson's pkgconfig module wants to see a library here, not an internal -# dependency object -libsysprof_ui_pkg_deps += libsysprof - -libsysprof_ui = shared_library( - 'sysprof-ui-@0@'.format(libsysprof_ui_api_version), - libsysprof_ui_public_sources + libsysprof_ui_private_sources + libsysprof_ui_resources, - - dependencies: libsysprof_ui_deps + [librax_dep], - install_dir: get_option('libdir'), - install: true, - c_args: [ '-DSYSPROF_UI_COMPILATION' ], - gnu_symbol_visibility: 'hidden', -) - -libsysprof_ui_dep = declare_dependency( - link_with: libsysprof_ui, - dependencies: libsysprof_ui_deps, - include_directories: include_directories('.'), -) -meson.override_dependency('sysprof-ui-@0@'.format(libsysprof_api_version), libsysprof_ui_dep) - -pkgconfig.generate( - libsysprof_ui, - subdirs: [ sysprof_ui_header_subdir ], - description: 'The UI library for GTK applications embedding sysprof', - install_dir: join_paths(get_option('libdir'), 'pkgconfig'), - requires: [ 'gio-2.0', 'gtk4' ], - libraries_private: libsysprof_ui_pkg_deps, - variables: [ - 'datadir=' + datadir_for_pc_file, - ], -) - -install_headers(libsysprof_ui_public_headers, subdir: sysprof_ui_header_subdir) diff --git a/src/libsysprof-ui/pointcache.c b/src/libsysprof-ui/pointcache.c deleted file mode 100644 index c16304ff..00000000 --- a/src/libsysprof-ui/pointcache.c +++ /dev/null @@ -1,116 +0,0 @@ -/* pointcache.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "pointcache" - -#include "pointcache.h" - -struct _PointCache -{ - volatile gint ref_count; - GHashTable *sets; -}; - -static void -point_cache_finalize (PointCache *self) -{ - g_clear_pointer (&self->sets, g_hash_table_unref); - g_slice_free (PointCache, self); -} - -PointCache * -point_cache_ref (PointCache *self) -{ - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (self->ref_count > 0, NULL); - - g_atomic_int_inc (&self->ref_count); - - return self; -} - -void -point_cache_unref (PointCache *self) -{ - g_return_if_fail (self != NULL); - g_return_if_fail (self->ref_count > 0); - - if (g_atomic_int_dec_and_test (&self->ref_count)) - point_cache_finalize (self); -} - -PointCache * -point_cache_new (void) -{ - PointCache *self; - - self = g_slice_new0 (PointCache); - self->ref_count = 1; - self->sets = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_array_unref); - - return self; -} - -void -point_cache_add_set (PointCache *self, - guint set_id) -{ - g_hash_table_insert (self->sets, - GUINT_TO_POINTER (set_id), - g_array_new (FALSE, FALSE, sizeof (Point))); -} - -gboolean -point_cache_contains_set (PointCache *self, - guint set_id) -{ - return g_hash_table_contains (self->sets, GUINT_TO_POINTER (set_id)); -} - -void -point_cache_add_point_to_set (PointCache *self, - guint set_id, - gdouble x, - gdouble y) -{ - GArray *ar; - Point point = { x, y }; - - ar = g_hash_table_lookup (self->sets, GUINT_TO_POINTER (set_id)); - g_array_append_val (ar, point); -} - -const Point * -point_cache_get_points (PointCache *self, - guint set_id, - guint *n_points) -{ - GArray *ar; - - *n_points = 0; - - if ((ar = g_hash_table_lookup (self->sets, GUINT_TO_POINTER (set_id)))) - { - *n_points = ar->len; - return &g_array_index (ar, const Point, 0); - } - - return NULL; -} diff --git a/src/libsysprof-ui/pointcache.h b/src/libsysprof-ui/pointcache.h deleted file mode 100644 index 3aed8179..00000000 --- a/src/libsysprof-ui/pointcache.h +++ /dev/null @@ -1,52 +0,0 @@ -/* pointcache.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef struct _PointCache PointCache; - -typedef struct -{ - gdouble x; - gdouble y; -} Point; - -PointCache *point_cache_new (void); -PointCache *point_cache_ref (PointCache *self); -void point_cache_unref (PointCache *self); -void point_cache_add_set (PointCache *self, - guint set_id); -gboolean point_cache_contains_set (PointCache *self, - guint set_id); -void point_cache_add_point_to_set (PointCache *self, - guint set_id, - gdouble x, - gdouble y); -const Point *point_cache_get_points (PointCache *self, - guint set_id, - guint *n_points); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (PointCache, point_cache_unref) - -G_END_DECLS diff --git a/src/libsysprof-ui/rectangles.c b/src/libsysprof-ui/rectangles.c deleted file mode 100644 index 9db41038..00000000 --- a/src/libsysprof-ui/rectangles.c +++ /dev/null @@ -1,248 +0,0 @@ -/* rectangles.c - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "rectangles.h" -#include "sysprof-color-cycle.h" -#include "sysprof-visualizer.h" - -typedef struct -{ - const gchar *name; - const gchar *message; - gint64 begin; - gint64 end; - GdkRectangle area; -} Rectangle; - -struct _Rectangles -{ - GStringChunk *strings; - GArray *rectangles; - GHashTable *y_indexes; - GHashTable *colors; - SysprofColorCycle *cycle; - gint64 begin_time; - gint64 end_time; - guint sorted : 1; -}; - -Rectangles * -rectangles_new (gint64 begin_time, - gint64 end_time) -{ - Rectangles *self; - - self = g_slice_new0 (Rectangles); - self->strings = g_string_chunk_new (4096); - self->rectangles = g_array_new (FALSE, FALSE, sizeof (Rectangle)); - self->y_indexes = g_hash_table_new (g_str_hash, g_str_equal); - self->colors = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); - self->cycle = sysprof_color_cycle_new (); - self->begin_time = begin_time; - self->end_time = end_time; - self->sorted = FALSE; - - return self; -} - -void -rectangles_add (Rectangles *self, - gint64 begin_time, - gint64 end_time, - const gchar *name, - const gchar *message) -{ - Rectangle rect = {0}; - - g_assert (self != NULL); - - if (message != NULL) - rect.message = g_string_chunk_insert_const (self->strings, message); - - if (name != NULL) - rect.name = g_string_chunk_insert_const (self->strings, name); - - rect.begin = begin_time; - rect.end = end_time; - - if (rect.end == rect.begin) - rect.area.width = 1; - - g_array_append_val (self->rectangles, rect); - - self->sorted = FALSE; -} - -void -rectangles_free (Rectangles *self) -{ - g_string_chunk_free (self->strings); - g_array_unref (self->rectangles); - g_hash_table_unref (self->colors); - g_hash_table_unref (self->y_indexes); - sysprof_color_cycle_unref (self->cycle); - g_slice_free (Rectangles, self); -} - -static gint -sort_rectangles (gconstpointer a, - gconstpointer b) -{ - const Rectangle *r1 = a; - const Rectangle *r2 = b; - gint64 r = r1->begin - r2->begin; - if (r == 0) - r = r1->end - r2->end; - if (r > 0) return 1; - else if (r < 0) return -1; - else return 0; -} - -static void -rectangles_sort (Rectangles *self) -{ - guint sequence = 0; - - g_assert (self != NULL); - - if (self->sorted) - return; - - g_array_sort (self->rectangles, sort_rectangles); - - g_hash_table_remove_all (self->y_indexes); - - for (guint i = 0; i < self->rectangles->len; i++) - { - const Rectangle *rect = &g_array_index (self->rectangles, Rectangle, i); - - if (!g_hash_table_contains (self->y_indexes, rect->name)) - { - GdkRGBA rgba; - - sysprof_color_cycle_next (self->cycle, &rgba); - g_hash_table_insert (self->y_indexes, (gchar *)rect->name, GUINT_TO_POINTER (++sequence)); - g_hash_table_insert (self->colors, (gchar *)rect->name, g_memdup2 (&rgba, sizeof rgba)); - } - } - - self->sorted = TRUE; -} - -void -rectangles_draw (Rectangles *self, - GtkWidget *row, - cairo_t *cr) -{ - GtkAllocation alloc; - gdouble range; - guint ns; - - g_assert (self != NULL); - g_assert (SYSPROF_IS_VISUALIZER (row)); - g_assert (cr != NULL); - - if (!self->sorted) - rectangles_sort (self); - - gtk_widget_get_allocation (row, &alloc); - ns = g_hash_table_size (self->y_indexes); - if (ns == 0 || alloc.height == 0) - return; - - range = self->end_time - self->begin_time; - - for (guint i = 0; i < self->rectangles->len; i++) - { - Rectangle *rect = &g_array_index (self->rectangles, Rectangle, i); - guint y_index = GPOINTER_TO_UINT (g_hash_table_lookup (self->y_indexes, rect->name)); - SysprofVisualizerRelativePoint in_points[2]; - SysprofVisualizerAbsolutePoint out_points[2]; - GdkRectangle r; - GdkRGBA *rgba; - - g_assert (y_index > 0); - g_assert (y_index <= ns); - - in_points[0].x = (rect->begin - self->begin_time) / range; - in_points[0].y = (y_index - 1) / (gdouble)ns; - in_points[1].x = (rect->end - self->begin_time) / range; - in_points[1].y = 0; - - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (row), - in_points, G_N_ELEMENTS (in_points), - out_points, G_N_ELEMENTS (out_points)); - - r.height = alloc.height / (gdouble)ns; - r.x = out_points[0].x; - r.y = out_points[0].y - r.height; - - if (rect->end <= rect->begin) - r.width = 1; - else - r.width = MAX (1, out_points[1].x - out_points[0].x); - - rect->area = r; - - rgba = g_hash_table_lookup (self->colors, rect->name); - - gdk_cairo_rectangle (cr, &r); - gdk_cairo_set_source_rgba (cr, rgba); - cairo_fill (cr); - } -} - -void -rectangles_set_end_time (Rectangles *self, - gint64 end_time) -{ - /* We might not know the real end time until we've exhausted the stream */ - self->end_time = end_time; -} - -gboolean -rectangles_query_tooltip (Rectangles *self, - GtkTooltip *tooltip, - const gchar *group, - gint x, - gint y) -{ - g_assert (self != NULL); - g_assert (GTK_IS_TOOLTIP (tooltip)); - - /* TODO: Sort, binary search, etc. */ - - for (guint i = 0; i < self->rectangles->len; i++) - { - const Rectangle *r = &g_array_index (self->rectangles, Rectangle, i); - - if (r->area.x <= x && - r->area.y <= y && - r->area.x + r->area.width >= x && - r->area.y + r->area.height >= y) - { - g_autofree gchar *text = g_strdup_printf ("%s:%s: %s", group, r->name, r->message); - gtk_tooltip_set_text (tooltip, text); - return TRUE; - } - } - - return FALSE; -} diff --git a/src/libsysprof-ui/rectangles.h b/src/libsysprof-ui/rectangles.h deleted file mode 100644 index f71c3a43..00000000 --- a/src/libsysprof-ui/rectangles.h +++ /dev/null @@ -1,48 +0,0 @@ -/* rectangles.h - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef struct _Rectangles Rectangles; - -Rectangles *rectangles_new (gint64 begin_time, - gint64 end_time); -void rectangles_free (Rectangles *self); -void rectangles_draw (Rectangles *self, - GtkWidget *widget, - cairo_t *cr); -void rectangles_add (Rectangles *self, - gint64 begin_time, - gint64 end_time, - const gchar *name, - const gchar *message); -void rectangles_set_end_time (Rectangles *self, - gint64 end_time); -gboolean rectangles_query_tooltip (Rectangles *self, - GtkTooltip *tooltip, - const gchar *group, - gint x, - gint y); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-aid-icon.c b/src/libsysprof-ui/sysprof-aid-icon.c deleted file mode 100644 index eba2b4ff..00000000 --- a/src/libsysprof-ui/sysprof-aid-icon.c +++ /dev/null @@ -1,211 +0,0 @@ -/* sysprof-aid-icon.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-aid-icon" - -#include "config.h" - -#include "sysprof-aid-icon.h" - -struct _SysprofAidIcon -{ - GtkFlowBoxChild parent_instance; - - SysprofAid *aid; - - /* Template Objects */ - GtkLabel *label; - GtkImage *image; - GtkImage *check; -}; - -G_DEFINE_TYPE (SysprofAidIcon, sysprof_aid_icon, GTK_TYPE_FLOW_BOX_CHILD) - -enum { - PROP_0, - PROP_AID, - PROP_SELECTED, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -/** - * sysprof_aid_icon_new: - * - * Create a new #SysprofAidIcon. - * - * Returns: (transfer full): a newly created #SysprofAidIcon - */ -GtkWidget * -sysprof_aid_icon_new (SysprofAid *aid) -{ - g_return_val_if_fail (SYSPROF_IS_AID (aid), NULL); - - return g_object_new (SYSPROF_TYPE_AID_ICON, - "aid", aid, - NULL); -} - -gboolean -sysprof_aid_icon_is_selected (SysprofAidIcon *self) -{ - g_return_val_if_fail (SYSPROF_IS_AID_ICON (self), FALSE); - - return gtk_widget_get_visible (GTK_WIDGET (self->check)); -} - -/** - * sysprof_aid_icon_get_aid: - * - * Get the aid that is represented by the icon. - * - * Returns: (transfer none): a #SysprofAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_aid_icon_get_aid (SysprofAidIcon *self) -{ - g_return_val_if_fail (SYSPROF_IS_AID_ICON (self), NULL); - - return self->aid; -} - -static void -sysprof_aid_icon_set_aid (SysprofAidIcon *self, - SysprofAid *aid) -{ - g_return_if_fail (SYSPROF_IS_AID_ICON (self)); - g_return_if_fail (SYSPROF_IS_AID (aid)); - - if (g_set_object (&self->aid, aid)) - { - GIcon *icon = sysprof_aid_get_icon (aid); - const gchar *title = sysprof_aid_get_display_name (aid); - - g_object_set (self->image, "gicon", icon, NULL); - gtk_label_set_label (self->label, title); - } -} - -static void -sysprof_aid_icon_finalize (GObject *object) -{ - SysprofAidIcon *self = (SysprofAidIcon *)object; - - g_clear_object (&self->aid); - - G_OBJECT_CLASS (sysprof_aid_icon_parent_class)->finalize (object); -} - -static void -sysprof_aid_icon_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofAidIcon *self = SYSPROF_AID_ICON (object); - - switch (prop_id) - { - case PROP_AID: - g_value_set_object (value, sysprof_aid_icon_get_aid (self)); - break; - - case PROP_SELECTED: - g_value_set_boolean (value, gtk_widget_get_visible (GTK_WIDGET (self->check))); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_aid_icon_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofAidIcon *self = SYSPROF_AID_ICON (object); - - switch (prop_id) - { - case PROP_AID: - sysprof_aid_icon_set_aid (self, g_value_get_object (value)); - break; - - case PROP_SELECTED: - gtk_widget_set_visible (GTK_WIDGET (self->check), g_value_get_boolean (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_aid_icon_class_init (SysprofAidIconClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->finalize = sysprof_aid_icon_finalize; - object_class->get_property = sysprof_aid_icon_get_property; - object_class->set_property = sysprof_aid_icon_set_property; - - properties [PROP_AID] = - g_param_spec_object ("aid", - "Aid", - "The aid for the icon", - SYSPROF_TYPE_AID, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_SELECTED] = - g_param_spec_boolean ("selected", - "Selected", - "If the item is selected", - FALSE, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_css_name (widget_class, "sysprofaidicon"); - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-aid-icon.ui"); - gtk_widget_class_bind_template_child (widget_class, SysprofAidIcon, check); - gtk_widget_class_bind_template_child (widget_class, SysprofAidIcon, image); - gtk_widget_class_bind_template_child (widget_class, SysprofAidIcon, label); -} - -static void -sysprof_aid_icon_init (SysprofAidIcon *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); -} - -void -sysprof_aid_icon_toggle (SysprofAidIcon *self) -{ - g_return_if_fail (SYSPROF_IS_AID_ICON (self)); - - gtk_widget_set_visible (GTK_WIDGET (self->check), - !gtk_widget_get_visible (GTK_WIDGET (self->check))); -} diff --git a/src/libsysprof-ui/sysprof-aid-icon.ui b/src/libsysprof-ui/sysprof-aid-icon.ui deleted file mode 100644 index 491cb405..00000000 --- a/src/libsysprof-ui/sysprof-aid-icon.ui +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/src/libsysprof-ui/sysprof-aid.c b/src/libsysprof-ui/sysprof-aid.c deleted file mode 100644 index 1bf3a489..00000000 --- a/src/libsysprof-ui/sysprof-aid.c +++ /dev/null @@ -1,353 +0,0 @@ -/* sysprof-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-aid" - -#include "config.h" - -#include - -#include "sysprof-aid.h" - -typedef struct -{ - GPtrArray *sources; - gchar *display_name; - GIcon *icon; -} SysprofAidPrivate; - -static void buildable_iface_init (GtkBuildableIface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofAid, sysprof_aid, G_TYPE_OBJECT, - G_ADD_PRIVATE (SysprofAid) - G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)) - -enum { - PROP_0, - PROP_DISPLAY_NAME, - PROP_ICON, - PROP_ICON_NAME, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_aid_real_present_async (SysprofAid *self, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_task_report_new_error (self, callback, user_data, - sysprof_aid_real_present_async, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "Not supported"); -} - -static gboolean -sysprof_aid_real_present_finish (SysprofAid *self, - GAsyncResult *result, - GError **error) -{ - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_aid_finalize (GObject *object) -{ - SysprofAid *self = (SysprofAid *)object; - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_clear_pointer (&priv->sources, g_ptr_array_unref); - g_clear_pointer (&priv->display_name, g_free); - g_clear_object (&priv->icon); - - G_OBJECT_CLASS (sysprof_aid_parent_class)->finalize (object); -} - -static void -sysprof_aid_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofAid *self = SYSPROF_AID (object); - - switch (prop_id) - { - case PROP_DISPLAY_NAME: - g_value_set_string (value, sysprof_aid_get_display_name (self)); - break; - - case PROP_ICON: - g_value_set_object (value, sysprof_aid_get_icon (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_aid_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofAid *self = SYSPROF_AID (object); - - switch (prop_id) - { - case PROP_DISPLAY_NAME: - sysprof_aid_set_display_name (self, g_value_get_string (value)); - break; - - case PROP_ICON: - sysprof_aid_set_icon (self, g_value_get_object (value)); - break; - - case PROP_ICON_NAME: - sysprof_aid_set_icon_name (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_aid_class_init (SysprofAidClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_aid_finalize; - object_class->get_property = sysprof_aid_get_property; - object_class->set_property = sysprof_aid_set_property; - - klass->present_async = sysprof_aid_real_present_async; - klass->present_finish = sysprof_aid_real_present_finish; - - properties [PROP_DISPLAY_NAME] = - g_param_spec_string ("display-name", - "Display Name", - "Display Name", - NULL, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_ICON_NAME] = - g_param_spec_string ("icon-name", - "Icon Name", - "Icon Name", - NULL, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_ICON] = - g_param_spec_object ("icon", - "Icon", - "The icon to display", - G_TYPE_ICON, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_aid_init (SysprofAid *self G_GNUC_UNUSED) -{ -} - -/** - * sysprof_aid_get_display_name: - * @self: a #SysprofAid - * - * Gets the display name as it should be shown to the user. - * - * Returns: a string containing the display name - * - * Since: 3.34 - */ -const gchar * -sysprof_aid_get_display_name (SysprofAid *self) -{ - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_AID (self), NULL); - - return priv->display_name; -} - -/** - * sysprof_aid_get_icon: - * - * Gets the icon for the aid. - * - * Returns: (transfer none) (nullable): a #GIcon or %NULL - * - * Since: 3.34 - */ -GIcon * -sysprof_aid_get_icon (SysprofAid *self) -{ - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_AID (self), NULL); - - return priv->icon; -} - -void -sysprof_aid_set_icon (SysprofAid *self, - GIcon *icon) -{ - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_AID (self)); - - if (g_set_object (&priv->icon, icon)) - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ICON]); -} - -void -sysprof_aid_set_icon_name (SysprofAid *self, - const gchar *icon_name) -{ - g_autoptr(GIcon) icon = NULL; - - g_return_if_fail (SYSPROF_IS_AID (self)); - - if (icon_name != NULL) - icon = g_themed_icon_new (icon_name); - - sysprof_aid_set_icon (self, icon); -} - -void -sysprof_aid_set_display_name (SysprofAid *self, - const gchar *display_name) -{ - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_AID (self)); - - if (g_strcmp0 (display_name, priv->display_name) != 0) - { - g_free (priv->display_name); - priv->display_name = g_strdup (display_name); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]); - } -} - -void -sysprof_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_AID (self)); - g_return_if_fail (SYSPROF_IS_PROFILER (profiler)); - - if (priv->sources != NULL) - { - for (guint i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - sysprof_profiler_add_source (profiler, source); - } - - if (priv->sources->len > 0) - g_ptr_array_remove_range (priv->sources, 0, priv->sources->len); - } - - if (SYSPROF_AID_GET_CLASS (self)->prepare) - SYSPROF_AID_GET_CLASS (self)->prepare (self, profiler); -} - -static void -sysprof_aid_add_child (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *object, - const gchar *type G_GNUC_UNUSED) -{ - SysprofAid *self = (SysprofAid *)buildable; - SysprofAidPrivate *priv = sysprof_aid_get_instance_private (self); - - g_assert (SYSPROF_IS_AID (self)); - g_assert (GTK_IS_BUILDER (builder)); - g_assert (G_IS_OBJECT (object)); - - if (SYSPROF_IS_SOURCE (object)) - { - if (priv->sources == NULL) - priv->sources = g_ptr_array_new_with_free_func (g_object_unref); - g_ptr_array_add (priv->sources, g_object_ref (object)); - return; - } - - g_warning ("Unsupported child type of %s: %s", - G_OBJECT_TYPE_NAME (self), - G_OBJECT_TYPE_NAME (object)); -} - -static void -buildable_iface_init (GtkBuildableIface *iface) -{ - iface->add_child = sysprof_aid_add_child; -} - -SysprofAid * -sysprof_aid_new (const gchar *display_name, - const gchar *icon_name) -{ - return g_object_new (SYSPROF_TYPE_AID, - "display-aid", display_name, - "icon-name", icon_name, - NULL); -} - -void -sysprof_aid_present_async (SysprofAid *self, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_return_if_fail (SYSPROF_IS_AID (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (SYSPROF_IS_DISPLAY (display)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - SYSPROF_AID_GET_CLASS (self)->present_async (self, reader, display, cancellable, callback, user_data); -} - -gboolean -sysprof_aid_present_finish (SysprofAid *self, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_AID (self), FALSE); - g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); - - return SYSPROF_AID_GET_CLASS (self)->present_finish (self, result, error); -} diff --git a/src/libsysprof-ui/sysprof-aid.h b/src/libsysprof-ui/sysprof-aid.h deleted file mode 100644 index 70a16667..00000000 --- a/src/libsysprof-ui/sysprof-aid.h +++ /dev/null @@ -1,76 +0,0 @@ -/* sysprof-aid.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -#include "sysprof-display.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_AID (sysprof_aid_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofAid, sysprof_aid, SYSPROF, AID, GObject) - -struct _SysprofAidClass -{ - GObjectClass parent_class; - - void (*prepare) (SysprofAid *self, - SysprofProfiler *profiler); - void (*present_async) (SysprofAid *self, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - gboolean (*present_finish) (SysprofAid *self, - GAsyncResult *result, - GError **error); - - /*< private >*/ - gpointer _reserved[16]; -}; - -SysprofAid *sysprof_aid_new (const gchar *display_name, - const gchar *icon_name); -const gchar *sysprof_aid_get_display_name (SysprofAid *self); -void sysprof_aid_set_display_name (SysprofAid *self, - const gchar *display_name); -GIcon *sysprof_aid_get_icon (SysprofAid *self); -void sysprof_aid_set_icon (SysprofAid *self, - GIcon *icon); -void sysprof_aid_set_icon_name (SysprofAid *self, - const gchar *icon_name); -void sysprof_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler); -void sysprof_aid_present_async (SysprofAid *self, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_aid_present_finish (SysprofAid *self, - GAsyncResult *result, - GError **error); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-battery-aid.c b/src/libsysprof-ui/sysprof-battery-aid.c deleted file mode 100644 index 9351200f..00000000 --- a/src/libsysprof-ui/sysprof-battery-aid.c +++ /dev/null @@ -1,242 +0,0 @@ -/* sysprof-battery-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-battery-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-battery-aid.h" -#include "sysprof-line-visualizer.h" - -struct _SysprofBatteryAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; -} Present; - -G_DEFINE_TYPE (SysprofBatteryAid, sysprof_battery_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -/** - * sysprof_battery_aid_new: - * - * Create a new #SysprofBatteryAid. - * - * Returns: (transfer full): a newly created #SysprofBatteryAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_battery_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_BATTERY_AID, NULL); -} - -static void -sysprof_battery_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ -#ifdef __linux__ - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_BATTERY_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_battery_source_new (); - sysprof_profiler_add_source (profiler, source); -#endif -} - -static bool -collect_battery_counters (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - GArray *counters = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (counters != NULL); - - for (guint i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *counter = &def->counters[i]; - - if (g_strcmp0 (counter->category, "Battery Charge") == 0) - g_array_append_vals (counters, counter, 1); - } - - return TRUE; -} - -static void -sysprof_battery_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *present = task_data; - g_autoptr(GArray) counters = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_BATTERY_AID (source_object)); - g_assert (present != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - sysprof_capture_cursor_foreach (present->cursor, collect_battery_counters, counters); - g_task_return_pointer (task, - g_steal_pointer (&counters), - (GDestroyNotify) g_array_unref); -} - -static void -sysprof_battery_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_CTRDEF }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_BATTERY_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (1, types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_battery_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_battery_aid_present_worker); -} - -static gboolean -sysprof_battery_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) counters = NULL; - Present *present; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_TASK (result)); - - present = g_task_get_task_data (G_TASK (result)); - - if ((counters = g_task_propagate_pointer (G_TASK (result), error))) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - SysprofVisualizerGroup *group; - guint found = 0; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "title", _("Battery Charge"), - "visible", TRUE, - NULL); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *ctr = &g_array_index (counters, SysprofCaptureCounter, i); - - if (g_strcmp0 (ctr->category, "Battery Charge") == 0) - { - g_autofree gchar *title = NULL; - gboolean is_combined = g_str_equal (ctr->name, "Combined"); - GtkWidget *row; - GdkRGBA rgba; - - if (is_combined) - title = g_strdup (_("Battery Charge (All)")); - else - title = g_strdup_printf ("Battery Charge (%s)", ctr->name); - - row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", title, - "height-request", 35, - "visible", is_combined, - NULL); - sysprof_color_cycle_next (cycle, &rgba); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - sysprof_visualizer_group_insert (group, - SYSPROF_VISUALIZER (row), - is_combined ? 0 : -1, - !is_combined); - - found++; - } - } - - if (found > 0) - sysprof_display_add_group (present->display, group); - else - g_object_unref (g_object_ref_sink (group)); - } - - return counters != NULL; -} - -static void -sysprof_battery_aid_class_init (SysprofBatteryAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_battery_aid_prepare; - aid_class->present_async = sysprof_battery_aid_present_async; - aid_class->present_finish = sysprof_battery_aid_present_finish; -} - -static void -sysprof_battery_aid_init (SysprofBatteryAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Battery")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "battery-low-charging-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-cairo.c b/src/libsysprof-ui/sysprof-cairo.c deleted file mode 100644 index 3d8f82a6..00000000 --- a/src/libsysprof-ui/sysprof-cairo.c +++ /dev/null @@ -1,71 +0,0 @@ -/* sysprof-cairo.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-ui-private.h" - -void -_sysprof_rounded_rectangle (cairo_t *cr, - const GdkRectangle *rect, - gint x_radius, - gint y_radius) -{ - gint x; - gint y; - gint width; - gint height; - gint x1, x2; - gint y1, y2; - gint xr1, xr2; - gint yr1, yr2; - - g_return_if_fail (cr); - g_return_if_fail (rect); - - x = rect->x; - y = rect->y; - width = rect->width; - height = rect->height; - - x1 = x; - x2 = x1 + width; - y1 = y; - y2 = y1 + height; - - x_radius = MIN (x_radius, width / 2.0); - y_radius = MIN (y_radius, width / 2.0); - - xr1 = x_radius; - xr2 = x_radius / 2.0; - yr1 = y_radius; - yr2 = y_radius / 2.0; - - cairo_move_to (cr, x1 + xr1, y1); - cairo_line_to (cr, x2 - xr1, y1); - cairo_curve_to (cr, x2 - xr2, y1, x2, y1 + yr2, x2, y1 + yr1); - cairo_line_to (cr, x2, y2 - yr1); - cairo_curve_to (cr, x2, y2 - yr2, x2 - xr2, y2, x2 - xr1, y2); - cairo_line_to (cr, x1 + xr1, y2); - cairo_curve_to (cr, x1 + xr2, y2, x1, y2 - yr2, x1, y2 - yr1); - cairo_line_to (cr, x1, y1 + yr1); - cairo_curve_to (cr, x1, y1 + yr2, x1 + xr2, y1, x1 + xr1, y1); - cairo_close_path (cr); -} diff --git a/src/libsysprof-ui/sysprof-callgraph-aid.c b/src/libsysprof-ui/sysprof-callgraph-aid.c deleted file mode 100644 index f12be766..00000000 --- a/src/libsysprof-ui/sysprof-callgraph-aid.c +++ /dev/null @@ -1,275 +0,0 @@ -/* sysprof-callgraph-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-callgraph-aid" - -#include "config.h" - -#include - -#include "sysprof-callgraph-aid.h" -#include "sysprof-callgraph-page.h" -#include "sysprof-depth-visualizer.h" - -struct _SysprofCallgraphAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; - guint has_samples : 1; -} Present; - -G_DEFINE_TYPE (SysprofCallgraphAid, sysprof_callgraph_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -static void -on_group_activated_cb (SysprofVisualizerGroup *group, - SysprofPage *page) -{ - SysprofDisplay *display; - - g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); - g_assert (SYSPROF_IS_PAGE (page)); - - display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY)); - sysprof_display_set_visible_page (display, page); -} - -/** - * sysprof_callgraph_aid_new: - * - * Create a new #SysprofCallgraphAid. - * - * Returns: (transfer full): a newly created #SysprofCallgraphAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_callgraph_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_CALLGRAPH_AID, NULL); -} - -static void -sysprof_callgraph_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ - g_assert (SYSPROF_IS_CALLGRAPH_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - -#ifdef __linux__ - { - const GPid *pids; - guint n_pids; - - if ((pids = sysprof_profiler_get_pids (profiler, &n_pids))) - { - for (guint i = 0; i < n_pids; i++) - { - g_autoptr(SysprofSource) source = NULL; - - source = sysprof_perf_source_new (); - sysprof_perf_source_set_target_pid (SYSPROF_PERF_SOURCE (source), pids[i]); - sysprof_profiler_add_source (profiler, source); - } - } - else - { - g_autoptr(SysprofSource) source = NULL; - - source = sysprof_perf_source_new (); - sysprof_profiler_add_source (profiler, source); - } - } -#endif -} - -static bool -discover_samples_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - Present *p = user_data; - - g_assert (frame != NULL); - g_assert (p != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_SAMPLE) - { - p->has_samples = TRUE; - return FALSE; - } - - return TRUE; -} - -static void -sysprof_callgraph_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *p = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_CALLGRAPH_AID (source_object)); - g_assert (p != NULL); - g_assert (p->cursor != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - /* If we find a sample frame, then we should enable the callgraph - * and stack visualizers. - */ - sysprof_capture_cursor_foreach (p->cursor, discover_samples_cb, p); - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_callgraph_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_SAMPLE }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_CALLGRAPH_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (1, types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_callgraph_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_callgraph_aid_present_worker); -} - -static gboolean -sysprof_callgraph_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - Present *p; - - g_assert (SYSPROF_IS_CALLGRAPH_AID (aid)); - g_assert (G_IS_TASK (result)); - - p = g_task_get_task_data (G_TASK (result)); - - if (p->has_samples) - { - SysprofVisualizerGroup *group; - SysprofVisualizer *depth; - SysprofPage *page; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "has-page", TRUE, - "priority", -500, - "title", _("Stack Traces"), - "visible", TRUE, - NULL); - - depth = sysprof_depth_visualizer_new (SYSPROF_DEPTH_VISUALIZER_COMBINED); - g_object_set (depth, - "title", _("Stack Traces"), - "height-request", 35, - "visible", TRUE, - NULL); - sysprof_visualizer_group_insert (group, depth, 0, FALSE); - - depth = sysprof_depth_visualizer_new (SYSPROF_DEPTH_VISUALIZER_KERNEL_ONLY); - g_object_set (depth, - "title", _("Stack Traces (In Kernel)"), - "height-request", 35, - "visible", FALSE, - NULL); - sysprof_visualizer_group_insert (group, depth, 1, TRUE); - - depth = sysprof_depth_visualizer_new (SYSPROF_DEPTH_VISUALIZER_USER_ONLY); - g_object_set (depth, - "title", _("Stack Traces (In User)"), - "height-request", 35, - "visible", FALSE, - NULL); - sysprof_visualizer_group_insert (group, depth, 2, TRUE); - - sysprof_display_add_group (p->display, group); - - page = g_object_new (SYSPROF_TYPE_CALLGRAPH_PAGE, - "title", _("Callgraph"), - "vexpand", TRUE, - "visible", TRUE, - NULL); - sysprof_display_add_page (p->display, page); - sysprof_display_set_visible_page (p->display, page); - - g_signal_connect_object (group, - "group-activated", - G_CALLBACK (on_group_activated_cb), - page, - 0); - } - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_callgraph_aid_class_init (SysprofCallgraphAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_callgraph_aid_prepare; - aid_class->present_async = sysprof_callgraph_aid_present_async; - aid_class->present_finish = sysprof_callgraph_aid_present_finish; -} - -static void -sysprof_callgraph_aid_init (SysprofCallgraphAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Callgraph")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-callgraph-page.c b/src/libsysprof-ui/sysprof-callgraph-page.c deleted file mode 100644 index 0cbabbb4..00000000 --- a/src/libsysprof-ui/sysprof-callgraph-page.c +++ /dev/null @@ -1,1299 +0,0 @@ -/* sysprof-callgraph-page.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, 2006, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include - -#include "../stackstash.h" - -#include "egg-paned-private.h" - -#include "sysprof-callgraph-page.h" -#include "sysprof-cell-renderer-percent.h" - -typedef struct -{ - SysprofCallgraphProfile *profile; - - GtkTreeView *callers_view; - GtkTreeView *functions_view; - GtkTreeView *descendants_view; - GtkTreeViewColumn *descendants_name_column; - GtkStack *stack; - GtkWidget *empty_state; - GtkWidget *loading_state; - GtkWidget *callgraph; - - GQueue *history; - - guint profile_size; - guint loading; -} SysprofCallgraphPagePrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofCallgraphPage, sysprof_callgraph_page, SYSPROF_TYPE_PAGE) - -enum { - PROP_0, - PROP_PROFILE, - N_PROPS -}; - -enum { - GO_PREVIOUS, - N_SIGNALS -}; - -enum { - COLUMN_NAME, - COLUMN_SELF, - COLUMN_TOTAL, - COLUMN_POINTER, - COLUMN_HITS, -}; - -static void sysprof_callgraph_page_update_descendants (SysprofCallgraphPage *self, - StackNode *node); - -static GParamSpec *properties [N_PROPS]; -static guint signals [N_SIGNALS]; - -static guint -sysprof_callgraph_page_get_profile_size (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - StackStash *stash; - StackNode *node; - guint size = 0; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - if (priv->profile_size != 0) - return priv->profile_size; - - if (priv->profile == NULL) - return 0; - - if (NULL == (stash = sysprof_callgraph_profile_get_stash (priv->profile))) - return 0; - - for (node = stack_stash_get_root (stash); node != NULL; node = node->siblings) - size += node->total; - - priv->profile_size = size; - - return size; -} - -static void -build_functions_store (StackNode *node, - gpointer user_data) -{ - struct { - GtkListStore *store; - gdouble profile_size; - } *state = user_data; - GtkTreeIter iter; - const StackNode *n; - guint size = 0; - guint total = 0; - - g_assert (state != NULL); - g_assert (GTK_IS_LIST_STORE (state->store)); - - for (n = node; n != NULL; n = n->next) - { - size += n->size; - if (n->toplevel) - total += n->total; - } - - gtk_list_store_append (state->store, &iter); - gtk_list_store_set (state->store, &iter, - COLUMN_NAME, U64_TO_POINTER(node->data), - COLUMN_SELF, 100.0 * size / state->profile_size, - COLUMN_TOTAL, 100.0 * total / state->profile_size, - COLUMN_POINTER, node, - -1); - -} - -static void -sysprof_callgraph_page_load (SysprofCallgraphPage *self, - SysprofCallgraphProfile *profile) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkListStore *functions; - StackStash *stash; - StackNode *n; - GtkTreeIter iter; - struct { - GtkListStore *store; - gdouble profile_size; - } state = { 0 }; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (profile)); - - /* - * TODO: This is probably the type of thing we want to do off the main - * thread. We should be able to build the tree models off thread - * and then simply apply them on the main thread. - * - * In the mean time, we should set the state of the widget to - * insensitive and give some indication of loading progress. - */ - - if (!g_set_object (&priv->profile, profile)) - return; - - if (sysprof_callgraph_profile_is_empty (profile)) - return; - - stash = sysprof_callgraph_profile_get_stash (profile); - - for (n = stack_stash_get_root (stash); n; n = n->siblings) - state.profile_size += n->total; - - functions = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_POINTER); - - state.store = functions; - stack_stash_foreach_by_address (stash, build_functions_store, &state); - - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (functions), - COLUMN_TOTAL, - GTK_SORT_DESCENDING); - - gtk_tree_view_set_model (priv->functions_view, GTK_TREE_MODEL (functions)); - gtk_tree_view_set_model (priv->callers_view, NULL); - gtk_tree_view_set_model (priv->descendants_view, NULL); - - if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (functions), &iter)) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (priv->functions_view); - gtk_tree_selection_select_iter (selection, &iter); - } - - gtk_stack_set_visible_child (priv->stack, priv->callgraph); - - g_clear_object (&functions); -} - -void -_sysprof_callgraph_page_set_failed (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - gtk_stack_set_visible_child (priv->stack, priv->empty_state); -} - -static void -sysprof_callgraph_page_unload (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (priv->profile)); - - g_queue_clear (priv->history); - g_clear_object (&priv->profile); - priv->profile_size = 0; - - gtk_tree_view_set_model (priv->callers_view, NULL); - gtk_tree_view_set_model (priv->functions_view, NULL); - gtk_tree_view_set_model (priv->descendants_view, NULL); - - gtk_stack_set_visible_child (priv->stack, priv->empty_state); -} - -/** - * sysprof_callgraph_page_get_profile: - * - * Returns: (transfer none): An #SysprofCallgraphProfile. - */ -SysprofCallgraphProfile * -sysprof_callgraph_page_get_profile (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self), NULL); - - return priv->profile; -} - -void -sysprof_callgraph_page_set_profile (SysprofCallgraphPage *self, - SysprofCallgraphProfile *profile) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_return_if_fail (!profile || SYSPROF_IS_CALLGRAPH_PROFILE (profile)); - - if (profile != priv->profile) - { - if (priv->profile) - sysprof_callgraph_page_unload (self); - - if (profile) - sysprof_callgraph_page_load (self, profile); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROFILE]); - } -} - -static void -sysprof_callgraph_page_expand_descendants (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeModel *model; - GList *all_paths = NULL; - GtkTreePath *first_path; - GtkTreeIter iter; - gdouble top_value = 0; - gint max_rows = 40; /* FIXME */ - gint n_rows; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - model = gtk_tree_view_get_model (priv->descendants_view); - first_path = gtk_tree_path_new_first (); - all_paths = g_list_prepend (all_paths, first_path); - n_rows = 1; - - gtk_tree_model_get_iter (model, &iter, first_path); - gtk_tree_model_get (model, &iter, - COLUMN_TOTAL, &top_value, - -1); - - while ((all_paths != NULL) && (n_rows < max_rows)) - { - GtkTreeIter best_iter; - GtkTreePath *best_path = NULL; - GList *list; - gdouble best_value = 0.0; - gint n_children; - gint i; - - for (list = all_paths; list != NULL; list = list->next) - { - GtkTreePath *path = list->data; - - g_assert (path != NULL); - - if (gtk_tree_model_get_iter (model, &iter, path)) - { - gdouble value; - - gtk_tree_model_get (model, &iter, - COLUMN_TOTAL, &value, - -1); - - if (value >= best_value) - { - best_value = value; - best_path = path; - best_iter = iter; - } - } - } - - n_children = gtk_tree_model_iter_n_children (model, &best_iter); - - if ((n_children > 0) && - ((best_value / top_value) > 0.04) && - ((n_children + gtk_tree_path_get_depth (best_path)) / (gdouble)max_rows) < (best_value / top_value)) - { - gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE); - n_rows += n_children; - - if (gtk_tree_path_get_depth (best_path) < 4) - { - GtkTreePath *path; - - path = gtk_tree_path_copy (best_path); - gtk_tree_path_down (path); - - for (i = 0; i < n_children; i++) - { - all_paths = g_list_prepend (all_paths, path); - - path = gtk_tree_path_copy (path); - gtk_tree_path_next (path); - } - - gtk_tree_path_free (path); - } - } - - all_paths = g_list_remove (all_paths, best_path); - - /* Always expand at least once */ - if ((all_paths == NULL) && (n_rows == 1)) - gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE); - - gtk_tree_path_free (best_path); - } - - g_list_free_full (all_paths, (GDestroyNotify)gtk_tree_path_free); -} - -typedef struct -{ - StackNode *node; - const gchar *name; - guint self; - guint total; -} Caller; - -static Caller * -caller_new (StackNode *node) -{ - Caller *c; - - c = g_slice_new (Caller); - c->name = U64_TO_POINTER (node->data); - c->self = 0; - c->total = 0; - c->node = node; - - return c; -} - -static void -caller_free (gpointer data) -{ - Caller *c = data; - g_slice_free (Caller, c); -} - -static void -sysprof_callgraph_page_function_selection_changed (SysprofCallgraphPage *self, - GtkTreeSelection *selection) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeModel *model = NULL; - GtkTreeIter iter; - GtkListStore *callers_store; - g_autoptr(GHashTable) callers = NULL; - g_autoptr(GHashTable) processed = NULL; - StackNode *callees = NULL; - StackNode *node; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (GTK_IS_TREE_SELECTION (selection)); - - if (!gtk_tree_selection_get_selected (selection, &model, &iter)) - { - gtk_tree_view_set_model (priv->callers_view, NULL); - gtk_tree_view_set_model (priv->descendants_view, NULL); - return; - } - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &callees, - -1); - - sysprof_callgraph_page_update_descendants (self, callees); - - callers_store = gtk_list_store_new (4, - G_TYPE_STRING, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_POINTER); - - callers = g_hash_table_new_full (NULL, NULL, NULL, caller_free); - processed = g_hash_table_new (NULL, NULL); - - for (node = callees; node != NULL; node = node->next) - { - Caller *c; - - if (!node->parent) - continue; - - c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data)); - - if (c == NULL) - { - c = caller_new (node->parent); - g_hash_table_insert (callers, (gpointer)c->name, c); - } - } - - for (node = callees; node != NULL; node = node->next) - { - StackNode *top_caller = node->parent; - StackNode *top_callee = node; - StackNode *n; - Caller *c; - - if (!node->parent) - continue; - - /* - * We could have a situation where the function was called in a - * reentrant fashion, so we want to take the top-most match in the - * stack. - */ - for (n = node; n && n->parent; n = n->parent) - { - if (n->data == node->data && n->parent->data == node->parent->data) - { - top_caller = n->parent; - top_callee = n; - } - } - - c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data)); - - g_assert (c != NULL); - - if (!g_hash_table_lookup (processed, top_caller)) - { - c->total += top_callee->total; - g_hash_table_insert (processed, top_caller, top_caller); - } - - c->self += node->size; - } - - { - GHashTableIter hiter; - gpointer key, value; - guint size = 0; - - size = MAX (1, sysprof_callgraph_page_get_profile_size (self)); - - g_hash_table_iter_init (&hiter, callers); - - while (g_hash_table_iter_next (&hiter, &key, &value)) - { - Caller *c = value; - - gtk_list_store_append (callers_store, &iter); - gtk_list_store_set (callers_store, &iter, - COLUMN_NAME, c->name, - COLUMN_SELF, c->self * 100.0 / size, - COLUMN_TOTAL, c->total * 100.0 / size, - COLUMN_POINTER, c->node, - -1); - } - } - - gtk_tree_view_set_model (priv->callers_view, GTK_TREE_MODEL (callers_store)); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (callers_store), - COLUMN_TOTAL, - GTK_SORT_DESCENDING); - - g_clear_object (&callers_store); -} - -static void -sysprof_callgraph_page_set_node (SysprofCallgraphPage *self, - StackNode *node) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeModel *model; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (node != NULL); - - if (priv->profile == NULL) - return; - - model = gtk_tree_view_get_model (priv->functions_view); - - if (gtk_tree_model_get_iter_first (model, &iter)) - { - do - { - StackNode *item = NULL; - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &item, - -1); - - if (item != NULL && item->data == node->data) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (priv->functions_view); - gtk_tree_selection_select_iter (selection, &iter); - - break; - } - } - while (gtk_tree_model_iter_next (model, &iter)); - } -} - -static void -sysprof_callgraph_page_descendant_activated (SysprofCallgraphPage *self, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkTreeView *tree_view) -{ - GtkTreeModel *model; - StackNode *node = NULL; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (GTK_IS_TREE_VIEW (tree_view)); - g_assert (path != NULL); - g_assert (GTK_IS_TREE_VIEW_COLUMN (column)); - - model = gtk_tree_view_get_model (tree_view); - - if (!gtk_tree_model_get_iter (model, &iter, path)) - return; - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &node, - -1); - - if (node != NULL) - sysprof_callgraph_page_set_node (self, node); -} - -static void -sysprof_callgraph_page_caller_activated (SysprofCallgraphPage *self, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkTreeView *tree_view) -{ - GtkTreeModel *model; - StackNode *node = NULL; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (GTK_IS_TREE_VIEW (tree_view)); - g_assert (path != NULL); - g_assert (GTK_IS_TREE_VIEW_COLUMN (column)); - - model = gtk_tree_view_get_model (tree_view); - - if (!gtk_tree_model_get_iter (model, &iter, path)) - return; - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &node, - -1); - - if (node != NULL) - sysprof_callgraph_page_set_node (self, node); -} - -static void -sysprof_callgraph_page_tag_data_func (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - SysprofCallgraphPage *self = data; - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - StackNode *node = NULL; - const gchar *str = NULL; - - if (priv->profile == NULL) - return; - - gtk_tree_model_get (model, iter, COLUMN_POINTER, &node, -1); - - if (node && node->data) - { - GQuark tag; - - tag = sysprof_callgraph_profile_get_tag (priv->profile, GSIZE_TO_POINTER (node->data)); - if (tag != 0) - str = g_quark_to_string (tag); - } - - g_object_set (cell, "text", str, NULL); -} - -static void -sysprof_callgraph_page_real_go_previous (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - StackNode *node; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - node = g_queue_pop_head (priv->history); - - if (NULL != (node = g_queue_peek_head (priv->history))) - sysprof_callgraph_page_set_node (self, node); -} - -static gboolean -descendants_view_move_cursor_cb (GtkTreeView *descendants_view, - GtkMovementStep step, - int direction, - gboolean extend, - gboolean modify, - gpointer user_data) -{ - if (step == GTK_MOVEMENT_VISUAL_POSITIONS) - { - GtkTreePath *path; - - gtk_tree_view_get_cursor (descendants_view, &path, NULL); - - if (direction == 1) - { - gtk_tree_view_expand_row (descendants_view, path, FALSE); - g_signal_stop_emission_by_name (descendants_view, "move-cursor"); - return FALSE; - } - else if (direction == -1) - { - gtk_tree_view_collapse_row (descendants_view, path); - g_signal_stop_emission_by_name (descendants_view, "move-cursor"); - return FALSE; - } - - gtk_tree_path_free (path); - } - - return TRUE; -} - -static void -copy_tree_view_selection_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - g_autofree gchar *name = NULL; - gchar sstr[16]; - gchar tstr[16]; - GString *str = data; - gdouble self; - gdouble total; - gint depth; - - g_assert (GTK_IS_TREE_MODEL (model)); - g_assert (path != NULL); - g_assert (iter != NULL); - g_assert (str != NULL); - - depth = gtk_tree_path_get_depth (path); - gtk_tree_model_get (model, iter, - COLUMN_NAME, &name, - COLUMN_SELF, &self, - COLUMN_TOTAL, &total, - -1); - - g_snprintf (sstr, sizeof sstr, "%.2lf%%", self); - g_snprintf (tstr, sizeof tstr, "%.2lf%%", total); - - g_string_append_printf (str, "[%8s] [%8s] ", sstr, tstr); - - for (gint i = 1; i < depth; i++) - g_string_append (str, " "); - g_string_append (str, name); - g_string_append_c (str, '\n'); -} - -static void -copy_tree_view_selection (GtkTreeView *tree_view) -{ - g_autoptr(GString) str = NULL; - GdkClipboard *clipboard; - - g_assert (GTK_IS_TREE_VIEW (tree_view)); - - str = g_string_new (" SELF TOTAL FUNCTION\n"); - gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (tree_view), - copy_tree_view_selection_cb, - str); - - clipboard = gtk_widget_get_clipboard (GTK_WIDGET (tree_view)); - gdk_clipboard_set_text (clipboard, str->str); -} - -static void -sysprof_callgraph_page_copy_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) -{ - SysprofCallgraphPage *self = (SysprofCallgraphPage *)widget; - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkRoot *toplevel; - GtkWidget *focus; - - g_assert (GTK_IS_WIDGET (widget)); - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - if (!(toplevel = gtk_widget_get_root (widget)) || - !GTK_IS_ROOT (toplevel) || - !(focus = gtk_root_get_focus (toplevel))) - return; - - if (focus == GTK_WIDGET (priv->descendants_view)) - copy_tree_view_selection (priv->descendants_view); - else if (focus == GTK_WIDGET (priv->callers_view)) - copy_tree_view_selection (priv->callers_view); - else if (focus == GTK_WIDGET (priv->functions_view)) - copy_tree_view_selection (priv->functions_view); -} - -static void -sysprof_callgraph_page_generate_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofProfile *profile = (SysprofProfile *)object; - SysprofCallgraphPage *self; - g_autoptr(GTask) task = user_data; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_PROFILE (profile)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - self = g_task_get_source_object (task); - - if (!sysprof_profile_generate_finish (profile, result, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - sysprof_callgraph_page_set_profile (self, SYSPROF_CALLGRAPH_PROFILE (profile)); -} - -static void -sysprof_callgraph_page_load_async (SysprofPage *page, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *filter, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofCallgraphPage *self = (SysprofCallgraphPage *)page; - g_autoptr(SysprofCaptureReader) copy = NULL; - g_autoptr(SysprofProfile) profile = NULL; - g_autoptr(GTask) task = NULL; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_SELECTION (selection)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_callgraph_page_load_async); - - copy = sysprof_capture_reader_copy (reader); - - profile = sysprof_callgraph_profile_new_with_selection (selection); - sysprof_profile_set_reader (profile, reader); - sysprof_profile_generate (profile, - cancellable, - sysprof_callgraph_page_generate_cb, - g_steal_pointer (&task)); -} - -static gboolean -sysprof_callgraph_page_load_finish (SysprofPage *page, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (page), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_callgraph_page_finalize (GObject *object) -{ - SysprofCallgraphPage *self = (SysprofCallgraphPage *)object; - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - g_clear_pointer (&priv->history, g_queue_free); - g_clear_object (&priv->profile); - - G_OBJECT_CLASS (sysprof_callgraph_page_parent_class)->finalize (object); -} - -static void -sysprof_callgraph_page_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofCallgraphPage *self = SYSPROF_CALLGRAPH_PAGE (object); - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - switch (prop_id) - { - case PROP_PROFILE: - g_value_set_object (value, priv->profile); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_callgraph_page_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofCallgraphPage *self = SYSPROF_CALLGRAPH_PAGE (object); - - switch (prop_id) - { - case PROP_PROFILE: - sysprof_callgraph_page_set_profile (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_callgraph_page_class_init (SysprofCallgraphPageClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofPageClass *page_class = SYSPROF_PAGE_CLASS (klass); - - object_class->finalize = sysprof_callgraph_page_finalize; - object_class->get_property = sysprof_callgraph_page_get_property; - object_class->set_property = sysprof_callgraph_page_set_property; - - page_class->load_async = sysprof_callgraph_page_load_async; - page_class->load_finish = sysprof_callgraph_page_load_finish; - - klass->go_previous = sysprof_callgraph_page_real_go_previous; - - properties [PROP_PROFILE] = - g_param_spec_object ("profile", - "Profile", - "The callgraph profile to view", - SYSPROF_TYPE_CALLGRAPH_PROFILE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - signals [GO_PREVIOUS] = - g_signal_new ("go-previous", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (SysprofCallgraphPageClass, go_previous), - NULL, NULL, NULL, G_TYPE_NONE, 0); - - gtk_widget_class_set_template_from_resource (widget_class, - "/org/gnome/sysprof/ui/sysprof-callgraph-page.ui"); - - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, callers_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, functions_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, descendants_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, descendants_name_column); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, stack); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, callgraph); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, empty_state); - gtk_widget_class_bind_template_child_private (widget_class, SysprofCallgraphPage, loading_state); - - gtk_widget_class_install_action (widget_class, "page.copy", NULL, sysprof_callgraph_page_copy_cb); - - gtk_widget_class_add_binding_action (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, "page.copy", NULL); - gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Left, GDK_ALT_MASK, "go-previous", NULL); - - g_type_ensure (EGG_TYPE_PANED); - g_type_ensure (SYSPROF_TYPE_CELL_RENDERER_PERCENT); -} - -static void -sysprof_callgraph_page_init (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeSelection *selection; - GtkCellRenderer *cell; - - priv->history = g_queue_new (); - - gtk_widget_init_template (GTK_WIDGET (self)); - - gtk_stack_set_visible_child (priv->stack, priv->loading_state); - - selection = gtk_tree_view_get_selection (priv->functions_view); - - g_signal_connect_object (selection, - "changed", - G_CALLBACK (sysprof_callgraph_page_function_selection_changed), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->descendants_view, - "row-activated", - G_CALLBACK (sysprof_callgraph_page_descendant_activated), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->callers_view, - "row-activated", - G_CALLBACK (sysprof_callgraph_page_caller_activated), - self, - G_CONNECT_SWAPPED); - - g_signal_connect (priv->descendants_view, - "move-cursor", - G_CALLBACK (descendants_view_move_cursor_cb), - NULL); - - cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, - "ellipsize", PANGO_ELLIPSIZE_MIDDLE, - "xalign", 0.0f, - NULL); - gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, TRUE); - gtk_tree_view_column_add_attribute (priv->descendants_name_column, cell, "text", 0); - - cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, - "foreground", "#666666", - "scale", PANGO_SCALE_SMALL, - "xalign", 1.0f, - NULL); - gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, FALSE); - gtk_tree_view_column_set_cell_data_func (priv->descendants_name_column, cell, - sysprof_callgraph_page_tag_data_func, - self, NULL); - - gtk_tree_selection_set_mode (gtk_tree_view_get_selection (priv->descendants_view), - GTK_SELECTION_MULTIPLE); -} - -typedef struct _Descendant Descendant; - -struct _Descendant -{ - const gchar *name; - guint self; - guint cumulative; - Descendant *parent; - Descendant *siblings; - Descendant *children; -}; - -static void -build_tree_cb (StackLink *trace, - gint size, - gpointer user_data) -{ - Descendant **tree = user_data; - Descendant *parent = NULL; - StackLink *link; - - g_assert (trace != NULL); - g_assert (tree != NULL); - - /* Get last item */ - link = trace; - while (link->next) - link = link->next; - - for (; link != NULL; link = link->prev) - { - const gchar *address = U64_TO_POINTER (link->data); - Descendant *prev = NULL; - Descendant *match = NULL; - - for (match = *tree; match != NULL; match = match->siblings) - { - if (match->name == address) - { - if (prev != NULL) - { - /* Move to front */ - prev->siblings = match->siblings; - match->siblings = *tree; - *tree = match; - } - break; - } - } - - if (match == NULL) - { - /* Have we seen this object further up the tree? */ - for (match = parent; match != NULL; match = match->parent) - { - if (match->name == address) - break; - } - } - - if (match == NULL) - { - match = g_slice_new (Descendant); - match->name = address; - match->cumulative = 0; - match->self = 0; - match->children = NULL; - match->parent = parent; - match->siblings = *tree; - *tree = match; - } - - tree = &match->children; - parent = match; - } - - parent->self += size; - - for (; parent != NULL; parent = parent->parent) - parent->cumulative += size; -} - -static Descendant * -build_tree (StackNode *node) -{ - Descendant *tree = NULL; - - for (; node != NULL; node = node->next) - { - if (node->toplevel) - stack_node_foreach_trace (node, build_tree_cb, &tree); - } - - return tree; -} - -static void -append_to_tree_and_free (SysprofCallgraphPage *self, - StackStash *stash, - GtkTreeStore *store, - Descendant *item, - GtkTreeIter *parent) -{ - StackNode *node = NULL; - GtkTreeIter iter; - guint profile_size; - - g_assert (GTK_IS_TREE_STORE (store)); - g_assert (item != NULL); - - profile_size = MAX (1, sysprof_callgraph_page_get_profile_size (self)); - - gtk_tree_store_append (store, &iter, parent); - - node = stack_stash_find_node (stash, (gpointer)item->name); - - gtk_tree_store_set (store, &iter, - COLUMN_NAME, item->name, - COLUMN_SELF, item->self * 100.0 / (gdouble)profile_size, - COLUMN_TOTAL, item->cumulative * 100.0 / (gdouble)profile_size, - COLUMN_POINTER, node, - COLUMN_HITS, (guint)item->cumulative, - -1); - - if (item->siblings != NULL) - append_to_tree_and_free (self, stash, store, item->siblings, parent); - - if (item->children != NULL) - append_to_tree_and_free (self, stash, store, item->children, &iter); - - g_slice_free (Descendant, item); -} - -static void -sysprof_callgraph_page_update_descendants (SysprofCallgraphPage *self, - StackNode *node) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeStore *store; - - g_assert (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - if (g_queue_peek_head (priv->history) != node) - g_queue_push_head (priv->history, node); - - store = gtk_tree_store_new (5, - G_TYPE_STRING, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_POINTER, - G_TYPE_UINT); - - if (priv->profile != NULL) - { - StackStash *stash; - - stash = sysprof_callgraph_profile_get_stash (priv->profile); - if (stash != NULL) - { - Descendant *tree; - - tree = build_tree (node); - if (tree != NULL) - append_to_tree_and_free (self, stash, store, tree, NULL); - } - } - - gtk_tree_view_set_model (priv->descendants_view, GTK_TREE_MODEL (store)); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), - COLUMN_TOTAL, GTK_SORT_DESCENDING); - sysprof_callgraph_page_expand_descendants (self); - - g_clear_object (&store); -} - -/** - * sysprof_callgraph_page_screenshot: - * @self: A #SysprofCallgraphPage. - * - * This function will generate a text representation of the descendants tree. - * This is useful if you want to include various profiling information in a - * commit message or email. - * - * The text generated will match the current row expansion in the tree view. - * - * Returns: (nullable) (transfer full): A newly allocated string that should be freed - * with g_free(). - */ -gchar * -sysprof_callgraph_page_screenshot (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeView *tree_view; - GtkTreeModel *model; - GtkTreePath *tree_path; - GString *str; - GtkTreeIter iter; - - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self), NULL); - - tree_view = priv->descendants_view; - - if (NULL == (model = gtk_tree_view_get_model (tree_view))) - return NULL; - - /* - * To avoid having to precalculate the deepest visible row, we - * put the timing information at the beginning of the line. - */ - - str = g_string_new (" SELF CUMULATIVE FUNCTION\n"); - tree_path = gtk_tree_path_new_first (); - - for (;;) - { - if (gtk_tree_model_get_iter (model, &iter, tree_path)) - { - guint depth = gtk_tree_path_get_depth (tree_path); - StackNode *node; - gdouble in_self; - gdouble total; - guint i; - - gtk_tree_model_get (model, &iter, - COLUMN_SELF, &in_self, - COLUMN_TOTAL, &total, - COLUMN_POINTER, &node, - -1); - - g_string_append_printf (str, "[% 7.2lf%%] [% 7.2lf%%] ", in_self, total); - - for (i = 0; i < depth; i++) - g_string_append (str, " "); - g_string_append (str, GSIZE_TO_POINTER (node->data)); - g_string_append_c (str, '\n'); - - if (gtk_tree_view_row_expanded (tree_view, tree_path)) - gtk_tree_path_down (tree_path); - else - gtk_tree_path_next (tree_path); - - continue; - } - - if (!gtk_tree_path_up (tree_path) || !gtk_tree_path_get_depth (tree_path)) - break; - - gtk_tree_path_next (tree_path); - } - - gtk_tree_path_free (tree_path); - - return g_string_free (str, FALSE); -} - -guint -sysprof_callgraph_page_get_n_functions (SysprofCallgraphPage *self) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - GtkTreeModel *model; - guint ret = 0; - - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self), 0); - - if (NULL != (model = gtk_tree_view_get_model (priv->functions_view))) - ret = gtk_tree_model_iter_n_children (model, NULL); - - return ret; -} - -void -_sysprof_callgraph_page_set_loading (SysprofCallgraphPage *self, - gboolean loading) -{ - SysprofCallgraphPagePrivate *priv = sysprof_callgraph_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_CALLGRAPH_PAGE (self)); - - if (loading) - priv->loading++; - else - priv->loading--; - - if (priv->loading) - gtk_stack_set_visible_child (priv->stack, priv->loading_state); - else - gtk_stack_set_visible_child (priv->stack, priv->callgraph); -} diff --git a/src/libsysprof-ui/sysprof-callgraph-page.h b/src/libsysprof-ui/sysprof-callgraph-page.h deleted file mode 100644 index 7624c580..00000000 --- a/src/libsysprof-ui/sysprof-callgraph-page.h +++ /dev/null @@ -1,51 +0,0 @@ -/* sysprof-callgraph-page.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -#include "sysprof-page.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CALLGRAPH_PAGE (sysprof_callgraph_page_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofCallgraphPage, sysprof_callgraph_page, SYSPROF, CALLGRAPH_PAGE, SysprofPage) - -struct _SysprofCallgraphPageClass -{ - SysprofPageClass parent_class; - - void (*go_previous) (SysprofCallgraphPage *self); - - /*< private >*/ - gpointer _reserved[16]; -}; - -GtkWidget *sysprof_callgraph_page_new (void); -SysprofCallgraphProfile *sysprof_callgraph_page_get_profile (SysprofCallgraphPage *self); -void sysprof_callgraph_page_set_profile (SysprofCallgraphPage *self, - SysprofCallgraphProfile *profile); -gchar *sysprof_callgraph_page_screenshot (SysprofCallgraphPage *self); -guint sysprof_callgraph_page_get_n_functions (SysprofCallgraphPage *self); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-callgraph-page.ui b/src/libsysprof-ui/sysprof-callgraph-page.ui deleted file mode 100644 index b0dc4f26..00000000 --- a/src/libsysprof-ui/sysprof-callgraph-page.ui +++ /dev/null @@ -1,212 +0,0 @@ - - - diff --git a/src/libsysprof-ui/sysprof-cell-renderer-duration.c b/src/libsysprof-ui/sysprof-cell-renderer-duration.c deleted file mode 100644 index 3e60166e..00000000 --- a/src/libsysprof-ui/sysprof-cell-renderer-duration.c +++ /dev/null @@ -1,455 +0,0 @@ -/* sysprof-cell-renderer-duration.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-cell-renderer-duration" - -#include "config.h" - -#include "sysprof-cell-renderer-duration.h" -#include "sysprof-ui-private.h" -#include "sysprof-zoom-manager.h" - -typedef struct -{ - gint64 capture_begin_time; - gint64 capture_end_time; - gint64 capture_duration; - gint64 begin_time; - gint64 end_time; - gchar *text; - SysprofZoomManager *zoom_manager; - GdkRGBA color; - guint color_set : 1; -} SysprofCellRendererDurationPrivate; - -enum { - PROP_0, - PROP_BEGIN_TIME, - PROP_CAPTURE_BEGIN_TIME, - PROP_CAPTURE_END_TIME, - PROP_COLOR, - PROP_END_TIME, - PROP_TEXT, - PROP_ZOOM_MANAGER, - N_PROPS -}; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofCellRendererDuration, sysprof_cell_renderer_duration, GTK_TYPE_CELL_RENDERER) - -static GParamSpec *properties [N_PROPS]; - -static inline void -rounded_rectangle (cairo_t *cr, - const GdkRectangle *rect, - int x_radius, - int y_radius) -{ - int x; - int y; - int width; - int height; - int x1, x2; - int y1, y2; - int xr1, xr2; - int yr1, yr2; - - g_assert (cr); - g_assert (rect); - - x = rect->x; - y = rect->y; - width = rect->width; - height = rect->height; - - x1 = x; - x2 = x1 + width; - y1 = y; - y2 = y1 + height; - - x_radius = MIN (x_radius, width / 2.0); - y_radius = MIN (y_radius, width / 2.0); - - xr1 = x_radius; - xr2 = x_radius / 2.0; - yr1 = y_radius; - yr2 = y_radius / 2.0; - - cairo_move_to (cr, x1 + xr1, y1); - cairo_line_to (cr, x2 - xr1, y1); - cairo_curve_to (cr, x2 - xr2, y1, x2, y1 + yr2, x2, y1 + yr1); - cairo_line_to (cr, x2, y2 - yr1); - cairo_curve_to (cr, x2, y2 - yr2, x2 - xr2, y2, x2 - xr1, y2); - cairo_line_to (cr, x1 + xr1, y2); - cairo_curve_to (cr, x1 + xr2, y2, x1, y2 - yr2, x1, y2 - yr1); - cairo_line_to (cr, x1, y1 + yr1); - cairo_curve_to (cr, x1, y1 + yr2, x1 + xr2, y1, x1 + xr1, y1); - cairo_close_path (cr); -} - -static void -sysprof_cell_renderer_duration_snapshot (GtkCellRenderer *renderer, - GtkSnapshot *snapshot, - GtkWidget *widget, - const GdkRectangle *bg_area, - const GdkRectangle *cell_area, - GtkCellRendererState state) -{ - SysprofCellRendererDuration *self = (SysprofCellRendererDuration *)renderer; - SysprofCellRendererDurationPrivate *priv = sysprof_cell_renderer_duration_get_instance_private (self); - g_autoptr(GString) str = NULL; - GtkStyleContext *style_context; - cairo_t *cr; - gdouble x1, x2; - GdkRGBA rgba; - GdkRectangle r; - gint64 duration; - - g_assert (SYSPROF_IS_CELL_RENDERER_DURATION (self)); - g_assert (snapshot != NULL); - g_assert (GTK_IS_WIDGET (widget)); - - if (priv->zoom_manager == NULL) - return; - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (cell_area->x, cell_area->y, cell_area->width, cell_area->height)); - - style_context = gtk_widget_get_style_context (widget); - - if (priv->color_set) - rgba = priv->color; - else - gtk_style_context_get_color (style_context, &rgba); - - duration = sysprof_zoom_manager_get_duration_for_width (priv->zoom_manager, bg_area->width); - - x1 = (priv->begin_time - priv->capture_begin_time) / (gdouble)duration * cell_area->width; - x2 = (priv->end_time - priv->capture_begin_time) / (gdouble)duration * cell_area->width; - - if (x2 < x1) - x2 = x1; - - r.x = cell_area->x + x1; - r.height = 12; - r.y = cell_area->y + (cell_area->height - r.height) / 2; - r.width = MAX (1.0, x2 - x1); - - if ((cell_area->height - r.height) % 2 == 1) - r.height++; - - gdk_cairo_set_source_rgba (cr, &rgba); - - if (r.width > 3) - { - rounded_rectangle (cr, &r, 2, 2); - cairo_fill (cr); - } - else if (r.width > 1) - { - gdk_cairo_rectangle (cr, &r); - cairo_fill (cr); - } - else - { - cairo_set_line_width (cr, 1); - cairo_move_to (cr, r.x + .5, r.y); - cairo_line_to (cr, r.x + .5, r.y + r.height); - cairo_stroke (cr); - } - - str = g_string_new (NULL); - - if (priv->begin_time != priv->end_time) - { - g_autofree gchar *fmt = _sysprof_format_duration (priv->end_time - priv->begin_time); - g_string_append_printf (str, "%s — ", fmt); - } - - if (priv->text != NULL) - g_string_append (str, priv->text); - - if (str->len) - { - PangoLayout *layout; - gint w, h; - - /* Add some spacing before/after */ - r.x -= 24; - r.width += 48; - - layout = gtk_widget_create_pango_layout (widget, NULL); - pango_layout_set_text (layout, str->str, str->len); - pango_layout_get_pixel_size (layout, &w, &h); - - if ((r.x + r.width + w) < (cell_area->x + cell_area->width) || - ((cell_area->x + w) > r.x)) - cairo_move_to (cr, r.x + r.width, r.y + ((r.height - h) / 2)); - else - cairo_move_to (cr, r.x - w, r.y + ((r.height - h) / 2)); - - if (priv->end_time < priv->begin_time) - { - gdk_rgba_parse (&rgba, "#f00"); - if (state & GTK_CELL_RENDERER_SELECTED) - rgba.alpha = 0.6; - } - - gdk_cairo_set_source_rgba (cr, &rgba); - pango_cairo_show_layout (cr, layout); - - g_object_unref (layout); - } - - cairo_destroy (cr); -} - -static GtkSizeRequestMode -sysprof_cell_renderer_duration_get_request_mode (GtkCellRenderer *renderer) -{ - return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; -} - -static void -sysprof_cell_renderer_duration_get_preferred_width (GtkCellRenderer *cell, - GtkWidget *widget, - gint *min_width, - gint *nat_width) -{ - SysprofCellRendererDuration *self = (SysprofCellRendererDuration *)cell; - SysprofCellRendererDurationPrivate *priv = sysprof_cell_renderer_duration_get_instance_private (self); - gint width = 1; - - g_assert (SYSPROF_IS_CELL_RENDERER_DURATION (self)); - g_assert (GTK_IS_WIDGET (widget)); - - GTK_CELL_RENDERER_CLASS (sysprof_cell_renderer_duration_parent_class)->get_preferred_width (cell, widget, min_width, nat_width); - - if (priv->zoom_manager && priv->capture_begin_time && priv->capture_end_time) - width = sysprof_zoom_manager_get_width_for_duration (priv->zoom_manager, - priv->capture_end_time - priv->capture_begin_time); - - if (min_width) - *min_width = width; - - if (nat_width) - *nat_width = width; -} - -static void -sysprof_cell_renderer_duration_get_preferred_height_for_width (GtkCellRenderer *cell, - GtkWidget *widget, - gint width, - gint *min_height, - gint *nat_height) -{ - PangoLayout *layout; - gint w, h; - gint ypad; - - g_assert (SYSPROF_IS_CELL_RENDERER_DURATION (cell)); - - gtk_cell_renderer_get_padding (cell, NULL, &ypad); - - layout = gtk_widget_create_pango_layout (widget, "XMZ09"); - pango_layout_get_pixel_size (layout, &w, &h); - g_clear_object (&layout); - - if (min_height) - *min_height = h + (ypad * 2); - - if (nat_height) - *nat_height = h + (ypad * 2); -} - -static void -sysprof_cell_renderer_duration_finalize (GObject *object) -{ - SysprofCellRendererDuration *self = (SysprofCellRendererDuration *)object; - SysprofCellRendererDurationPrivate *priv = sysprof_cell_renderer_duration_get_instance_private (self); - - g_clear_object (&priv->zoom_manager); - g_clear_pointer (&priv->text, g_free); - - G_OBJECT_CLASS (sysprof_cell_renderer_duration_parent_class)->finalize (object); -} - -static void -sysprof_cell_renderer_duration_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofCellRendererDuration *self = SYSPROF_CELL_RENDERER_DURATION (object); - SysprofCellRendererDurationPrivate *priv = sysprof_cell_renderer_duration_get_instance_private (self); - - switch (prop_id) - { - case PROP_BEGIN_TIME: - g_value_set_int64 (value, priv->begin_time); - break; - - case PROP_CAPTURE_BEGIN_TIME: - g_value_set_int64 (value, priv->capture_begin_time); - break; - - case PROP_CAPTURE_END_TIME: - g_value_set_int64 (value, priv->capture_end_time); - break; - - case PROP_END_TIME: - g_value_set_int64 (value, priv->end_time); - break; - - case PROP_TEXT: - g_value_set_string (value, priv->text); - break; - - case PROP_ZOOM_MANAGER: - g_value_set_object (value, priv->zoom_manager); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_cell_renderer_duration_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofCellRendererDuration *self = SYSPROF_CELL_RENDERER_DURATION (object); - SysprofCellRendererDurationPrivate *priv = sysprof_cell_renderer_duration_get_instance_private (self); - - switch (prop_id) - { - case PROP_BEGIN_TIME: - priv->begin_time = g_value_get_int64 (value); - break; - - case PROP_CAPTURE_BEGIN_TIME: - priv->capture_begin_time = g_value_get_int64 (value); - priv->capture_duration = priv->capture_end_time - priv->capture_begin_time; - break; - - case PROP_CAPTURE_END_TIME: - priv->capture_end_time = g_value_get_int64 (value); - priv->capture_duration = priv->capture_end_time - priv->capture_begin_time; - break; - - case PROP_COLOR: - if (g_value_get_boxed (value)) - priv->color = *(GdkRGBA *)g_value_get_boxed (value); - else - gdk_rgba_parse (&priv->color, "#000"); - priv->color_set = !!g_value_get_boolean (value); - break; - - case PROP_END_TIME: - priv->end_time = g_value_get_int64 (value); - break; - - case PROP_TEXT: - g_free (priv->text); - priv->text = g_value_dup_string (value); - break; - - case PROP_ZOOM_MANAGER: - g_set_object (&priv->zoom_manager, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_cell_renderer_duration_class_init (SysprofCellRendererDurationClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); - - object_class->finalize = sysprof_cell_renderer_duration_finalize; - object_class->get_property = sysprof_cell_renderer_duration_get_property; - object_class->set_property = sysprof_cell_renderer_duration_set_property; - - cell_class->get_preferred_height_for_width = sysprof_cell_renderer_duration_get_preferred_height_for_width; - cell_class->get_preferred_width = sysprof_cell_renderer_duration_get_preferred_width; - cell_class->get_request_mode = sysprof_cell_renderer_duration_get_request_mode; - cell_class->snapshot = sysprof_cell_renderer_duration_snapshot; - - /* Note we do not emit ::notify() for these properties */ - - properties [PROP_BEGIN_TIME] = - g_param_spec_int64 ("begin-time", NULL, NULL, - G_MININT64, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CAPTURE_BEGIN_TIME] = - g_param_spec_int64 ("capture-begin-time", NULL, NULL, - G_MININT64, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CAPTURE_END_TIME] = - g_param_spec_int64 ("capture-end-time", NULL, NULL, - G_MININT64, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_COLOR] = - g_param_spec_boxed ("color", NULL, NULL, - GDK_TYPE_RGBA, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_END_TIME] = - g_param_spec_int64 ("end-time", NULL, NULL, - G_MININT64, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_END_TIME] = - g_param_spec_int64 ("end-time", NULL, NULL, - G_MININT64, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_TEXT] = - g_param_spec_string ("text", NULL, NULL, - NULL, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_ZOOM_MANAGER] = - g_param_spec_object ("zoom-manager", NULL, NULL, - SYSPROF_TYPE_ZOOM_MANAGER, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_cell_renderer_duration_init (SysprofCellRendererDuration *self) -{ - SysprofCellRendererDurationPrivate *priv = sysprof_cell_renderer_duration_get_instance_private (self); - - priv->color.alpha = 1.0; -} - -GtkCellRenderer * -sysprof_cell_renderer_duration_new (void) -{ - return g_object_new (SYSPROF_TYPE_CELL_RENDERER_DURATION, NULL); -} diff --git a/src/libsysprof-ui/sysprof-cell-renderer-percent.c b/src/libsysprof-ui/sysprof-cell-renderer-percent.c deleted file mode 100644 index e23e1fed..00000000 --- a/src/libsysprof-ui/sysprof-cell-renderer-percent.c +++ /dev/null @@ -1,139 +0,0 @@ -/* sysprof-cell-renderer-percent.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-cell-renderer-percent" - -#include "config.h" - -#include - -#include "sysprof-cell-renderer-percent.h" - -typedef struct -{ - gdouble percent; -} SysprofCellRendererPercentPrivate; - -enum { - PROP_0, - PROP_PERCENT, - N_PROPS -}; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofCellRendererPercent, sysprof_cell_renderer_percent, SYSPROF_TYPE_CELL_RENDERER_PROGRESS) - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_cell_renderer_percent_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofCellRendererPercent *self = SYSPROF_CELL_RENDERER_PERCENT (object); - - switch (prop_id) - { - case PROP_PERCENT: - g_value_set_double (value, sysprof_cell_renderer_percent_get_percent (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_cell_renderer_percent_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofCellRendererPercent *self = SYSPROF_CELL_RENDERER_PERCENT (object); - - switch (prop_id) - { - case PROP_PERCENT: - sysprof_cell_renderer_percent_set_percent (self, g_value_get_double (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_cell_renderer_percent_class_init (SysprofCellRendererPercentClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->get_property = sysprof_cell_renderer_percent_get_property; - object_class->set_property = sysprof_cell_renderer_percent_set_property; - - properties [PROP_PERCENT] = - g_param_spec_double ("percent", - "Percent", - "Percent", - 0.0, - 100.0, - 0.0, - /* Doesn't notify to avoid signal emission */ - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_cell_renderer_percent_init (SysprofCellRendererPercent *self) -{ - g_object_set (self, "text-xalign", 1.0f, NULL); -} - -gdouble -sysprof_cell_renderer_percent_get_percent (SysprofCellRendererPercent *self) -{ - SysprofCellRendererPercentPrivate *priv = sysprof_cell_renderer_percent_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_CELL_RENDERER_PERCENT (self), 0.0); - - return priv->percent; -} - -void -sysprof_cell_renderer_percent_set_percent (SysprofCellRendererPercent *self, - gdouble percent) -{ - SysprofCellRendererPercentPrivate *priv = sysprof_cell_renderer_percent_get_instance_private (self); - gchar text[8]; - - g_return_if_fail (SYSPROF_IS_CELL_RENDERER_PERCENT (self)); - g_return_if_fail (percent >= 0.0); - g_return_if_fail (percent <= 100.0); - - priv->percent = percent; - - g_snprintf (text, sizeof text, "%.2lf%%", percent); - text [sizeof text - 1] = '\0'; - - g_object_set (self, - "value", (guint)percent, - "text", text, - NULL); -} diff --git a/src/libsysprof-ui/sysprof-cell-renderer-percent.h b/src/libsysprof-ui/sysprof-cell-renderer-percent.h deleted file mode 100644 index 44a8fc04..00000000 --- a/src/libsysprof-ui/sysprof-cell-renderer-percent.h +++ /dev/null @@ -1,57 +0,0 @@ -/* sysprof-cell-renderer-percent.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-cell-renderer-progress.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CELL_RENDERER_PERCENT (sysprof_cell_renderer_percent_get_type()) -#define SYSPROF_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SYSPROF_TYPE_CELL_RENDERER_PERCENT, SysprofCellRendererPercent)) -#define SYSPROF_CELL_RENDERER_PERCENT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SYSPROF_TYPE_CELL_RENDERER_PERCENT, SysprofCellRendererPercent const)) -#define SYSPROF_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SYSPROF_TYPE_CELL_RENDERER_PERCENT, SysprofCellRendererPercentClass)) -#define SYSPROF_IS_CELL_RENDERER_PERCENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SYSPROF_TYPE_CELL_RENDERER_PERCENT)) -#define SYSPROF_IS_CELL_RENDERER_PERCENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SYSPROF_TYPE_CELL_RENDERER_PERCENT)) -#define SYSPROF_CELL_RENDERER_PERCENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SYSPROF_TYPE_CELL_RENDERER_PERCENT, SysprofCellRendererPercentClass)) - -typedef struct _SysprofCellRendererPercent SysprofCellRendererPercent; -typedef struct _SysprofCellRendererPercentClass SysprofCellRendererPercentClass; - -struct _SysprofCellRendererPercent -{ - SysprofCellRendererProgress parent; -}; - -struct _SysprofCellRendererPercentClass -{ - SysprofCellRendererProgressClass parent_class; - - /*< private >*/ - gpointer _reserved[4]; -}; - -GType sysprof_cell_renderer_percent_get_type (void); -GtkCellRenderer *sysprof_cell_renderer_percent_new (void); -gdouble sysprof_cell_renderer_percent_get_percent (SysprofCellRendererPercent *self); -void sysprof_cell_renderer_percent_set_percent (SysprofCellRendererPercent *self, - gdouble percent); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-cell-renderer-progress.c b/src/libsysprof-ui/sysprof-cell-renderer-progress.c deleted file mode 100644 index a0943b64..00000000 --- a/src/libsysprof-ui/sysprof-cell-renderer-progress.c +++ /dev/null @@ -1,712 +0,0 @@ -/* gtkcellrendererprogress.c - * Copyright (C) 2002 Naba Kumar - * heavily modified by Jörgen Scheibengruber - * heavily modified by Marco Pesenti Gritti - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library. If not, see . - */ -/* - * Modified by the GTK+ Team and others 1997-2007. See the AUTHORS - * file for a list of people on the GTK+ Team. See the ChangeLog - * files for a list of changes. These files are distributed with - * GTK+ at ftp://ftp.gtk.org/pub/gtk/. - */ - -#include "config.h" - -#include -#include - -#include "sysprof-cell-renderer-progress.h" - - -enum -{ - PROP_0, - PROP_VALUE, - PROP_TEXT, - PROP_PULSE, - PROP_TEXT_XALIGN, - PROP_TEXT_YALIGN, - PROP_ORIENTATION, - PROP_INVERTED -}; - -struct _SysprofCellRendererProgressPrivate -{ - int value; - char *text; - char *label; - int min_h; - int min_w; - int pulse; - int offset; - float text_xalign; - float text_yalign; - GtkOrientation orientation; - gboolean inverted; -}; - -static void sysprof_cell_renderer_progress_finalize (GObject *object); -static void sysprof_cell_renderer_progress_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec); -static void sysprof_cell_renderer_progress_set_property (GObject *object, - guint param_id, - const GValue *value, - GParamSpec *pspec); -static void sysprof_cell_renderer_progress_set_value (SysprofCellRendererProgress *cellprogress, - int value); -static void sysprof_cell_renderer_progress_set_text (SysprofCellRendererProgress *cellprogress, - const char *text); -static void sysprof_cell_renderer_progress_set_pulse (SysprofCellRendererProgress *cellprogress, - int pulse); -static void compute_dimensions (GtkCellRenderer *cell, - GtkWidget *widget, - const char *text, - int *width, - int *height); -static void sysprof_cell_renderer_progress_snapshot (GtkCellRenderer *cell, - GtkSnapshot *snapshot, - GtkWidget *widget, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - GtkCellRendererState flags); - - -G_DEFINE_TYPE_WITH_CODE (SysprofCellRendererProgress, sysprof_cell_renderer_progress, GTK_TYPE_CELL_RENDERER, - G_ADD_PRIVATE (SysprofCellRendererProgress) - G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)) - -static void -recompute_label (SysprofCellRendererProgress *cellprogress) -{ - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - char *label; - - if (priv->text) - label = g_strdup (priv->text); - else if (priv->pulse < 0) - label = g_strdup_printf (C_("progress bar label", "%d %%"), priv->value); - else - label = NULL; - - g_free (priv->label); - priv->label = label; -} - -static void -sysprof_cell_renderer_progress_set_value (SysprofCellRendererProgress *cellprogress, - int value) -{ - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - - if (priv->value != value) - { - priv->value = value; - recompute_label (cellprogress); - g_object_notify (G_OBJECT (cellprogress), "value"); - } -} - -static void -sysprof_cell_renderer_progress_set_text (SysprofCellRendererProgress *cellprogress, - const char *text) -{ - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - char *new_text; - - new_text = g_strdup (text); - g_free (priv->text); - priv->text = new_text; - recompute_label (cellprogress); - g_object_notify (G_OBJECT (cellprogress), "text"); -} - -static void -sysprof_cell_renderer_progress_set_pulse (SysprofCellRendererProgress *cellprogress, - int pulse) -{ - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - - if (pulse != priv->pulse) - { - if (pulse <= 0) - priv->offset = 0; - else - priv->offset = pulse; - g_object_notify (G_OBJECT (cellprogress), "pulse"); - } - - priv->pulse = pulse; - recompute_label (cellprogress); -} - -static void -sysprof_cell_renderer_progress_finalize (GObject *object) -{ - SysprofCellRendererProgress *cellprogress = SYSPROF_CELL_RENDERER_PROGRESS (object); - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - - g_free (priv->text); - g_free (priv->label); - - G_OBJECT_CLASS (sysprof_cell_renderer_progress_parent_class)->finalize (object); -} - -static void -sysprof_cell_renderer_progress_get_property (GObject *object, - guint param_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofCellRendererProgress *cellprogress = SYSPROF_CELL_RENDERER_PROGRESS (object); - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - - switch (param_id) - { - case PROP_VALUE: - g_value_set_int (value, priv->value); - break; - case PROP_TEXT: - g_value_set_string (value, priv->text); - break; - case PROP_PULSE: - g_value_set_int (value, priv->pulse); - break; - case PROP_TEXT_XALIGN: - g_value_set_float (value, priv->text_xalign); - break; - case PROP_TEXT_YALIGN: - g_value_set_float (value, priv->text_yalign); - break; - case PROP_ORIENTATION: - g_value_set_enum (value, priv->orientation); - break; - case PROP_INVERTED: - g_value_set_boolean (value, priv->inverted); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - } -} - -static void -sysprof_cell_renderer_progress_set_property (GObject *object, - guint param_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofCellRendererProgress *cellprogress = SYSPROF_CELL_RENDERER_PROGRESS (object); - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - - switch (param_id) - { - case PROP_VALUE: - sysprof_cell_renderer_progress_set_value (cellprogress, - g_value_get_int (value)); - break; - case PROP_TEXT: - sysprof_cell_renderer_progress_set_text (cellprogress, - g_value_get_string (value)); - break; - case PROP_PULSE: - sysprof_cell_renderer_progress_set_pulse (cellprogress, - g_value_get_int (value)); - break; - case PROP_TEXT_XALIGN: - priv->text_xalign = g_value_get_float (value); - break; - case PROP_TEXT_YALIGN: - priv->text_yalign = g_value_get_float (value); - break; - case PROP_ORIENTATION: - if (priv->orientation != g_value_get_enum (value)) - { - priv->orientation = g_value_get_enum (value); - g_object_notify_by_pspec (object, pspec); - } - break; - case PROP_INVERTED: - if (priv->inverted != g_value_get_boolean (value)) - { - priv->inverted = g_value_get_boolean (value); - g_object_notify_by_pspec (object, pspec); - } - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); - } -} - -static void -compute_dimensions (GtkCellRenderer *cell, - GtkWidget *widget, - const char *text, - int *width, - int *height) -{ - PangoRectangle logical_rect; - PangoLayout *layout; - int xpad, ypad; - - layout = gtk_widget_create_pango_layout (widget, text); - pango_layout_get_pixel_extents (layout, NULL, &logical_rect); - - gtk_cell_renderer_get_padding (cell, &xpad, &ypad); - - if (width) - *width = logical_rect.width + xpad * 2; - - if (height) - *height = logical_rect.height + ypad * 2; - - g_object_unref (layout); -} - -static void -sysprof_cell_renderer_progress_get_preferred_width (GtkCellRenderer *cell, - GtkWidget *widget, - int *minimum, - int *natural) -{ - SysprofCellRendererProgress *self = SYSPROF_CELL_RENDERER_PROGRESS (cell); - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (self); - int w, h; - int size; - - if (priv->min_w < 0) - { - char *text = g_strdup_printf (C_("progress bar label", "%d %%"), 100); - compute_dimensions (cell, widget, text, - &priv->min_w, - &priv->min_h); - g_free (text); - } - - compute_dimensions (cell, widget, priv->label, &w, &h); - - size = MAX (priv->min_w, w); - - if (minimum != NULL) - *minimum = size; - if (natural != NULL) - *natural = size; -} - -static void -sysprof_cell_renderer_progress_get_preferred_height (GtkCellRenderer *cell, - GtkWidget *widget, - int *minimum, - int *natural) -{ - SysprofCellRendererProgress *self = SYSPROF_CELL_RENDERER_PROGRESS (cell); - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (self); - int w, h; - int size; - - if (priv->min_w < 0) - { - char *text = g_strdup_printf (C_("progress bar label", "%d %%"), 100); - compute_dimensions (cell, widget, text, - &priv->min_w, - &priv->min_h); - g_free (text); - } - - compute_dimensions (cell, widget, priv->label, &w, &h); - - size = MIN (priv->min_h, h); - - if (minimum != NULL) - *minimum = size; - if (natural != NULL) - *natural = size; -} - -static inline int -get_bar_size (int pulse, - int value, - int full_size) -{ - int bar_size; - - if (pulse < 0) - bar_size = full_size * MAX (0, value) / 100; - else if (pulse == 0) - bar_size = 0; - else if (pulse == G_MAXINT) - bar_size = full_size; - else - bar_size = MAX (2, full_size / 5); - - return bar_size; -} - -static inline int -get_bar_position (int start, - int full_size, - int bar_size, - int pulse, - int offset, - gboolean is_rtl) -{ - int position; - - if (pulse < 0 || pulse == 0 || pulse == G_MAXINT) - { - position = is_rtl ? (start + full_size - bar_size) : start; - } - else - { - position = (is_rtl ? offset + 12 : offset) % 24; - if (position > 12) - position = 24 - position; - position = start + full_size * position / 15; - } - - return position; -} - -static void -sysprof_cell_renderer_progress_snapshot (GtkCellRenderer *cell, - GtkSnapshot *snapshot, - GtkWidget *widget, - const GdkRectangle *background_area, - const GdkRectangle *cell_area, - GtkCellRendererState flags) -{ - SysprofCellRendererProgress *cellprogress = SYSPROF_CELL_RENDERER_PROGRESS (cell); - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - GtkStyleContext *context; - GtkBorder padding; - PangoLayout *layout; - PangoRectangle logical_rect; - int x, y, w, h, x_pos, y_pos, bar_position, bar_size, start, full_size; - int xpad, ypad; - GdkRectangle clip; - gboolean is_rtl; - - context = gtk_widget_get_style_context (widget); - is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; - - gtk_cell_renderer_get_padding (cell, &xpad, &ypad); - x = cell_area->x + xpad; - y = cell_area->y + ypad; - w = cell_area->width - xpad * 2; - h = cell_area->height - ypad * 2; - - gtk_style_context_save (context); - gtk_style_context_add_class (context, "trough"); - - gtk_snapshot_render_background (snapshot, context, x, y, w, h); - gtk_snapshot_render_frame (snapshot, context, x, y, w, h); - - gtk_style_context_get_padding (context, &padding); - - x += padding.left; - y += padding.top; - w -= padding.left + padding.right; - h -= padding.top + padding.bottom; - - gtk_style_context_restore (context); - - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - { - clip.y = y; - clip.height = h; - - start = x; - full_size = w; - - bar_size = get_bar_size (priv->pulse, priv->value, full_size); - - if (!priv->inverted) - bar_position = get_bar_position (start, full_size, bar_size, - priv->pulse, priv->offset, is_rtl); - else - bar_position = get_bar_position (start, full_size, bar_size, - priv->pulse, priv->offset, !is_rtl); - - clip.width = bar_size; - clip.x = bar_position; - } - else - { - clip.x = x; - clip.width = w; - - start = y; - full_size = h; - - bar_size = get_bar_size (priv->pulse, priv->value, full_size); - - if (priv->inverted) - bar_position = get_bar_position (start, full_size, bar_size, - priv->pulse, priv->offset, TRUE); - else - bar_position = get_bar_position (start, full_size, bar_size, - priv->pulse, priv->offset, FALSE); - - clip.height = bar_size; - clip.y = bar_position; - } - - if (bar_size > 0) - { - gtk_style_context_save (context); - gtk_style_context_add_class (context, "progressbar"); - - gtk_snapshot_render_background (snapshot, context, clip.x, clip.y, clip.width, clip.height); - gtk_snapshot_render_frame (snapshot, context, clip.x, clip.y, clip.width, clip.height); - - gtk_style_context_restore (context); - } - - if (priv->label) - { - float text_xalign; - - layout = gtk_widget_create_pango_layout (widget, priv->label); - pango_layout_get_pixel_extents (layout, NULL, &logical_rect); - - if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_LTR) - text_xalign = 1.0 - priv->text_xalign; - else - text_xalign = priv->text_xalign; - - x_pos = x + padding.left + text_xalign * - (w - padding.left - padding.right - logical_rect.width); - - y_pos = y + padding.top + priv->text_yalign * - (h - padding.top - padding.bottom - logical_rect.height); - - gtk_snapshot_push_clip (snapshot, - &GRAPHENE_RECT_INIT( - clip.x, clip.y, - clip.width, clip.height - )); - - gtk_style_context_save (context); - gtk_style_context_add_class (context, "progressbar"); - - gtk_snapshot_render_layout (snapshot, context, - x_pos, y_pos, - layout); - - gtk_style_context_restore (context); - gtk_snapshot_pop (snapshot); - - gtk_style_context_save (context); - gtk_style_context_add_class (context, "trough"); - - if (bar_position > start) - { - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - { - clip.x = x; - clip.width = bar_position - x; - } - else - { - clip.y = y; - clip.height = bar_position - y; - } - - gtk_snapshot_push_clip (snapshot, - &GRAPHENE_RECT_INIT( - clip.x, clip.y, - clip.width, clip.height - )); - - gtk_snapshot_render_layout (snapshot, context, - x_pos, y_pos, - layout); - - gtk_snapshot_pop (snapshot); - } - - if (bar_position + bar_size < start + full_size) - { - if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) - { - clip.x = bar_position + bar_size; - clip.width = x + w - (bar_position + bar_size); - } - else - { - clip.y = bar_position + bar_size; - clip.height = y + h - (bar_position + bar_size); - } - - gtk_snapshot_push_clip (snapshot, - &GRAPHENE_RECT_INIT( - clip.x, clip.y, - clip.width, clip.height - )); - - gtk_snapshot_render_layout (snapshot, context, - x_pos, y_pos, - layout); - - gtk_snapshot_pop (snapshot); - } - - gtk_style_context_restore (context); - g_object_unref (layout); - } -} - -static void -sysprof_cell_renderer_progress_class_init (SysprofCellRendererProgressClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); - - object_class->finalize = sysprof_cell_renderer_progress_finalize; - object_class->get_property = sysprof_cell_renderer_progress_get_property; - object_class->set_property = sysprof_cell_renderer_progress_set_property; - - cell_class->get_preferred_width = sysprof_cell_renderer_progress_get_preferred_width; - cell_class->get_preferred_height = sysprof_cell_renderer_progress_get_preferred_height; - cell_class->snapshot = sysprof_cell_renderer_progress_snapshot; - - /** - * SysprofCellRendererProgress:value: - * - * The "value" property determines the percentage to which the - * progress bar will be "filled in". - **/ - g_object_class_install_property (object_class, - PROP_VALUE, - g_param_spec_int ("value", - "Value", - "Value of the progress bar", - 0, 100, 0, - G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); - - /** - * SysprofCellRendererProgress:text: - * - * The "text" property determines the label which will be drawn - * over the progress bar. Setting this property to %NULL causes the default - * label to be displayed. Setting this property to an empty string causes - * no label to be displayed. - **/ - g_object_class_install_property (object_class, - PROP_TEXT, - g_param_spec_string ("text", - "Text", - "Text on the progress bar", - NULL, - G_PARAM_READWRITE)); - - /** - * SysprofCellRendererProgress:pulse: - * - * Setting this to a non-negative value causes the cell renderer to - * enter "activity mode", where a block bounces back and forth to - * indicate that some progress is made, without specifying exactly how - * much. - * - * Each increment of the property causes the block to move by a little - * bit. - * - * To indicate that the activity has not started yet, set the property - * to zero. To indicate completion, set the property to %G_MAXINT. - */ - g_object_class_install_property (object_class, - PROP_PULSE, - g_param_spec_int ("pulse", - "Pulse", - "Set this to positive values to indicate that some progress is made, but you don’t know how much.", - -1, G_MAXINT, -1, - G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); - - /** - * SysprofCellRendererProgress:text-xalign: - * - * The "text-xalign" property controls the horizontal alignment of the - * text in the progress bar. Valid values range from 0 (left) to 1 - * (right). Reserved for RTL layouts. - */ - g_object_class_install_property (object_class, - PROP_TEXT_XALIGN, - g_param_spec_float ("text-xalign", - "Text x alignment", - "The horizontal text alignment, from 0 (left) to 1 (right). Reversed for RTL layouts.", - 0.0, 1.0, 0.5, - G_PARAM_READWRITE)); - - /** - * SysprofCellRendererProgress:text-yalign: - * - * The "text-yalign" property controls the vertical alignment of the - * text in the progress bar. Valid values range from 0 (top) to 1 - * (bottom). - */ - g_object_class_install_property (object_class, - PROP_TEXT_YALIGN, - g_param_spec_float ("text-yalign", - "Text y alignment", - "The vertical text alignment, from 0 (top) to 1 (bottom).", - 0.0, 1.0, 0.5, - G_PARAM_READWRITE)); - - g_object_class_override_property (object_class, - PROP_ORIENTATION, - "orientation"); - - g_object_class_install_property (object_class, - PROP_INVERTED, - g_param_spec_boolean ("inverted", - "Inverted", - "Invert the direction in which the progress bar grows", - FALSE, - G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); -} - -static void -sysprof_cell_renderer_progress_init (SysprofCellRendererProgress *cellprogress) -{ - SysprofCellRendererProgressPrivate *priv = sysprof_cell_renderer_progress_get_instance_private (cellprogress); - - priv->value = 0; - priv->text = NULL; - priv->label = NULL; - priv->min_w = -1; - priv->min_h = -1; - priv->pulse = -1; - priv->offset = 0; - - priv->text_xalign = 0.5; - priv->text_yalign = 0.5; - - priv->orientation = GTK_ORIENTATION_HORIZONTAL, - priv->inverted = FALSE; -} - -/** - * sysprof_cell_renderer_progress_new: - * - * Creates a new `SysprofCellRendererProgress`. - * - * Returns: the new cell renderer - **/ -GtkCellRenderer* -sysprof_cell_renderer_progress_new (void) -{ - return g_object_new (SYSPROF_TYPE_CELL_RENDERER_PROGRESS, NULL); -} diff --git a/src/libsysprof-ui/sysprof-cell-renderer-progress.h b/src/libsysprof-ui/sysprof-cell-renderer-progress.h deleted file mode 100644 index 7a006072..00000000 --- a/src/libsysprof-ui/sysprof-cell-renderer-progress.h +++ /dev/null @@ -1,53 +0,0 @@ -/* gtkcellrendererprogress.h - * Copyright (C) 2002 Naba Kumar - * modified by Jörgen Scheibengruber - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library. If not, see . - */ - -/* - * Modified by the GTK+ Team and others 1997-2004. See the AUTHORS - * file for a list of people on the GTK+ Team. See the ChangeLog - * files for a list of changes. These files are distributed with - * GTK+ at ftp://ftp.gtk.org/pub/gtk/. - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CELL_RENDERER_PROGRESS (sysprof_cell_renderer_progress_get_type ()) -#define SYSPROF_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SYSPROF_TYPE_CELL_RENDERER_PROGRESS, SysprofCellRendererProgress)) -#define SYSPROF_IS_CELL_RENDERER_PROGRESS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SYSPROF_TYPE_CELL_RENDERER_PROGRESS)) - -typedef struct _SysprofCellRendererProgress SysprofCellRendererProgress; -typedef struct _SysprofCellRendererProgressClass SysprofCellRendererProgressClass; -typedef struct _SysprofCellRendererProgressPrivate SysprofCellRendererProgressPrivate; - -struct _SysprofCellRendererProgress -{ - GtkCellRenderer parent_instance; -}; - -struct _SysprofCellRendererProgressClass -{ - GtkCellRendererClass parent_class; -}; - -GType sysprof_cell_renderer_progress_get_type (void) G_GNUC_CONST; -GtkCellRenderer *sysprof_cell_renderer_progress_new (void); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-check.c b/src/libsysprof-ui/sysprof-check.c deleted file mode 100644 index d7025790..00000000 --- a/src/libsysprof-ui/sysprof-check.c +++ /dev/null @@ -1,106 +0,0 @@ -/* sysprof-check.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-check" - -#include "config.h" - -#include "sysprof-check.h" - -static void -sysprof_check_supported_ping_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GDBusConnection *bus = (GDBusConnection *)object; - g_autoptr(GVariant) reply = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - - g_assert (G_IS_DBUS_CONNECTION (bus)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!(reply = g_dbus_connection_call_finish (bus, result, &error))) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_check_supported_bus_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(GDBusConnection) bus = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!(bus = g_bus_get_finish (result, &error))) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_dbus_connection_call (bus, - "org.gnome.Sysprof3", - "/org/gnome/Sysprof3", - "org.freedesktop.DBus.Peer", - "Ping", - g_variant_new ("()"), - NULL, - G_DBUS_CALL_FLAGS_NONE, - -1, - g_task_get_cancellable (task), - sysprof_check_supported_ping_cb, - g_object_ref (task)); -} - -void -sysprof_check_supported_async (GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (NULL, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_check_supported_async); - - /* Get access to the System D-Bus and check to see if we can ping the - * service that is found at org.gnome.Sysprof3. - */ - - g_bus_get (G_BUS_TYPE_SYSTEM, - cancellable, - sysprof_check_supported_bus_cb, - g_steal_pointer (&task)); - -} - -gboolean -sysprof_check_supported_finish (GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} diff --git a/src/libsysprof-ui/sysprof-check.h b/src/libsysprof-ui/sysprof-check.h deleted file mode 100644 index bb8f4e7c..00000000 --- a/src/libsysprof-ui/sysprof-check.h +++ /dev/null @@ -1,37 +0,0 @@ -/* sysprof-check.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -SYSPROF_AVAILABLE_IN_ALL -void sysprof_check_supported_async (GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_check_supported_finish (GAsyncResult *result, - GError **error); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-color-cycle.c b/src/libsysprof-ui/sysprof-color-cycle.c deleted file mode 100644 index 582878e9..00000000 --- a/src/libsysprof-ui/sysprof-color-cycle.c +++ /dev/null @@ -1,157 +0,0 @@ -/* sysprof-color-cycle.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-color-cycle" - -#include "config.h" - -#include "sysprof-color-cycle.h" - -G_DEFINE_BOXED_TYPE (SysprofColorCycle, sysprof_color_cycle, sysprof_color_cycle_ref, sysprof_color_cycle_unref) - -static const gchar *default_colors[] = { - - "#1a5fb4", /* Blue 5 */ - "#26a269", /* Green 5 */ - "#e5a50a", /* Yellow 5 */ - "#c64600", /* Orange 5 */ - "#a51d2d", /* Red 5 */ - "#613583", /* Purple 5 */ - "#63452c", /* Brown 5 */ - - "#1c71d8", /* Blue 4 */ - "#2ec27e", /* Green 4 */ - "#f5c211", /* Yellow 4 */ - "#e66100", /* Orange 4 */ - "#c01c28", /* Red 4 */ - "#813d9c", /* Purple 4 */ - "#865e3c", /* Brown 4 */ - - "#3584e4", /* Blue 3 */ - "#33d17a", /* Green 3 */ - "#f6d32d", /* Yellow 3 */ - "#ff7800", /* Orange 3 */ - "#e01b24", /* Red 3 */ - "#9141ac", /* Purple 3 */ - "#986a44", /* Brown 3 */ - - "#62a0ea", /* Blue 2 */ - "#57e389", /* Green 2 */ - "#f8e45c", /* Yellow 2 */ - "#ffa348", /* Orange 2 */ - "#ed333b", /* Red 2 */ - "#c061cb", /* Purple 2 */ - "#b5835a", /* Brown 2 */ - - "#99c1f1", /* Blue 1 */ - "#8ff0a4", /* Green 1 */ - "#f9f06b", /* Yellow 1 */ - "#ffbe6f", /* Orange 1 */ - "#f66151", /* Red 1 */ - "#dc8add", /* Purple 1 */ - "#cdab8f", /* Brown 1 */ - - NULL -}; - -struct _SysprofColorCycle -{ - volatile gint ref_count; - GdkRGBA *colors; - gsize n_colors; - guint position; -}; - -static void -sysprof_color_cycle_destroy (SysprofColorCycle *self) -{ - g_free (self->colors); - g_slice_free (SysprofColorCycle, self); -} - -SysprofColorCycle * -sysprof_color_cycle_new (void) -{ - SysprofColorCycle *self; - - self = g_slice_new0 (SysprofColorCycle); - self->ref_count = 1; - self->n_colors = g_strv_length ((gchar **)default_colors); - self->colors = g_new0 (GdkRGBA, self->n_colors); - - for (guint i = 0; default_colors[i]; i++) - { - if G_UNLIKELY (!gdk_rgba_parse (&self->colors[i], default_colors[i])) - g_warning ("Failed to parse color %s into an RGBA", default_colors[i]); - } - - return self; -} - -SysprofColorCycle * -sysprof_color_cycle_ref (SysprofColorCycle *self) -{ - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (self->ref_count > 0, NULL); - g_atomic_int_inc (&self->ref_count); - return self; -} - -void -sysprof_color_cycle_unref (SysprofColorCycle *self) -{ - g_return_if_fail (self != NULL); - g_return_if_fail (self->ref_count > 0); - if (g_atomic_int_dec_and_test (&self->ref_count)) - sysprof_color_cycle_destroy (self); -} - -void -sysprof_color_cycle_next (SysprofColorCycle *self, - GdkRGBA *rgba) -{ - g_return_if_fail (self != NULL); - g_return_if_fail (self->position < self->n_colors); - - *rgba = self->colors[self->position]; - - /* - * TODO: Adjust color HSV/etc - * - * We could simply adjust the brightness/etc after we dispatch - * a color so that we get darker as we go. - */ - - self->position = (self->position + 1) % self->n_colors; -} - -void -sysprof_color_cycle_reset (SysprofColorCycle *self) -{ - g_return_if_fail (self != NULL); - - for (guint i = 0; default_colors[i]; i++) - { - if G_UNLIKELY (!gdk_rgba_parse (&self->colors[i], default_colors[i])) - g_warning ("Failed to parse color %s into an RGBA", default_colors[i]); - } - - self->position = 0; -} diff --git a/src/libsysprof-ui/sysprof-color-cycle.h b/src/libsysprof-ui/sysprof-color-cycle.h deleted file mode 100644 index 650acb78..00000000 --- a/src/libsysprof-ui/sysprof-color-cycle.h +++ /dev/null @@ -1,41 +0,0 @@ -/* sysprof-color-cycle.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_COLOR_CYCLE (sysprof_color_cycle_get_type()) - -typedef struct _SysprofColorCycle SysprofColorCycle; - -GType sysprof_color_cycle_get_type (void); -SysprofColorCycle *sysprof_color_cycle_ref (SysprofColorCycle *self); -void sysprof_color_cycle_unref (SysprofColorCycle *self); -SysprofColorCycle *sysprof_color_cycle_new (void); -void sysprof_color_cycle_reset (SysprofColorCycle *self); -void sysprof_color_cycle_next (SysprofColorCycle *self, - GdkRGBA *rgba); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofColorCycle, sysprof_color_cycle_unref) - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-counters-aid.c b/src/libsysprof-ui/sysprof-counters-aid.c deleted file mode 100644 index 42bd04f0..00000000 --- a/src/libsysprof-ui/sysprof-counters-aid.c +++ /dev/null @@ -1,284 +0,0 @@ -/* sysprof-counters-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-counters-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-counters-aid.h" -#include "sysprof-line-visualizer.h" -#include "sysprof-marks-page.h" -#include "sysprof-time-visualizer.h" - -struct _SysprofCountersAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; -} Present; - -G_DEFINE_TYPE (SysprofCountersAid, sysprof_counters_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -static void -on_group_activated_cb (SysprofVisualizerGroup *group, - SysprofPage *page) -{ - SysprofDisplay *display; - - g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); - g_assert (SYSPROF_IS_PAGE (page)); - - display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY)); - sysprof_display_set_visible_page (display, page); -} - -/** - * sysprof_counters_aid_new: - * - * Create a new #SysprofCountersAid. - * - * Returns: (transfer full): a newly created #SysprofCountersAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_counters_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_COUNTERS_AID, NULL); -} - -static void -sysprof_counters_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ -} - -static gchar * -build_title (const SysprofCaptureCounter *ctr) -{ - GString *str; - - str = g_string_new (NULL); - - if (ctr->category[0] != 0) - { - if (str->len) - g_string_append_c (str, ' '); - g_string_append (str, ctr->category); - } - - if (ctr->name[0] != 0) - { - if (str->len) - g_string_append (str, " — "); - g_string_append (str, ctr->name); - } - - if (ctr->description[0] != 0) - { - if (str->len) - g_string_append_printf (str, " (%s)", ctr->description); - else - g_string_append (str, ctr->description); - } - - if (str->len == 0) - /* this is untranslated on purpose */ - g_string_append_printf (str, "Counter %d", ctr->id); - - return g_string_free (str, FALSE); -} - -static bool -collect_counters (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - GArray *counters = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (counters != NULL); - - if (def->n_counters > 0) - g_array_append_vals (counters, def->counters, def->n_counters); - - return TRUE; -} - -static void -sysprof_counters_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *present = task_data; - g_autoptr(GArray) counters = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_COUNTERS_AID (source_object)); - g_assert (present != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - sysprof_capture_cursor_foreach (present->cursor, collect_counters, counters); - g_task_return_pointer (task, - g_steal_pointer (&counters), - (GDestroyNotify) g_array_unref); -} - -static void -sysprof_counters_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_CTRDEF }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_COUNTERS_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (1, types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_counters_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_counters_aid_present_worker); -} - -static gboolean -sysprof_counters_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) counters = NULL; - Present *present; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_TASK (result)); - - present = g_task_get_task_data (G_TASK (result)); - - if ((counters = g_task_propagate_pointer (G_TASK (result), error)) && counters->len > 0) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - SysprofVisualizerGroup *group; - SysprofVisualizer *combined; - GtkWidget *page; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "has-page", TRUE, - "title", _("Counters"), - "visible", TRUE, - NULL); - - combined = g_object_new (SYSPROF_TYPE_TIME_VISUALIZER, - "title", _("Counters"), - "height-request", 35, - "visible", TRUE, - NULL); - sysprof_visualizer_group_insert (group, combined, -1, TRUE); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *ctr = &g_array_index (counters, SysprofCaptureCounter, i); - g_autofree gchar *title = build_title (ctr); - GtkWidget *row; - GdkRGBA rgba; - - row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", title, - "height-request", 35, - "visible", FALSE, - NULL); - sysprof_color_cycle_next (cycle, &rgba); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - rgba.alpha = .5; - sysprof_line_visualizer_set_fill (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - sysprof_time_visualizer_add_counter (SYSPROF_TIME_VISUALIZER (combined), ctr->id, &rgba); - sysprof_visualizer_group_insert (group, SYSPROF_VISUALIZER (row), -1, TRUE); - } - - sysprof_display_add_group (present->display, group); - - page = sysprof_marks_page_new (sysprof_display_get_zoom_manager (present->display), - SYSPROF_MARKS_MODEL_COUNTERS); - gtk_widget_show (page); - - g_signal_connect_object (group, - "group-activated", - G_CALLBACK (on_group_activated_cb), - page, - 0); - sysprof_display_add_page (present->display, SYSPROF_PAGE (page)); - } - - return counters != NULL; -} - -static void -sysprof_counters_aid_class_init (SysprofCountersAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_counters_aid_prepare; - aid_class->present_async = sysprof_counters_aid_present_async; - aid_class->present_finish = sysprof_counters_aid_present_finish; -} - -static void -sysprof_counters_aid_init (SysprofCountersAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Counters")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-cpu-aid.c b/src/libsysprof-ui/sysprof-cpu-aid.c deleted file mode 100644 index febdf876..00000000 --- a/src/libsysprof-ui/sysprof-cpu-aid.c +++ /dev/null @@ -1,357 +0,0 @@ -/* sysprof-cpu-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-cpu-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-cpu-aid.h" -#include "sysprof-line-visualizer.h" -#include "sysprof-procs-visualizer.h" - -struct _SysprofCpuAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; - GArray *counters; - guint has_processes : 1; -} Present; - -G_DEFINE_TYPE (SysprofCpuAid, sysprof_cpu_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_pointer (&p->counters, g_array_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -/** - * sysprof_cpu_aid_new: - * - * Create a new #SysprofCpuAid. - * - * Returns: (transfer full): a newly created #SysprofCpuAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_cpu_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_CPU_AID, NULL); -} - -static void -sysprof_cpu_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ -#ifdef __linux__ - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_CPU_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_hostinfo_source_new (); - sysprof_profiler_add_source (profiler, source); -#endif -} - -static bool -collect_info (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - Present *p = user_data; - - g_assert (frame != NULL); - g_assert (p != NULL); - g_assert (p->counters != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF) - { - for (guint i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *counter = &def->counters[i]; - - if (g_strcmp0 (counter->category, "CPU Percent") == 0 || - g_strcmp0 (counter->category, "CPU Frequency") == 0) - g_array_append_vals (p->counters, counter, 1); - } - } - else if (!p->has_processes && - (frame->type == SYSPROF_CAPTURE_FRAME_PROCESS || - frame->type == SYSPROF_CAPTURE_FRAME_EXIT)) - { - p->has_processes = TRUE; - } - - return TRUE; -} - -static void -sysprof_cpu_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *present = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_CPU_AID (source_object)); - g_assert (present != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - sysprof_capture_cursor_foreach (present->cursor, collect_info, present); - g_task_return_pointer (task, - g_steal_pointer (&present->counters), - (GDestroyNotify) g_array_unref); -} - -static void -sysprof_cpu_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_CTRDEF, - SYSPROF_CAPTURE_FRAME_PROCESS, - SYSPROF_CAPTURE_FRAME_EXIT, - }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_CPU_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - present.counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - present.has_processes = FALSE; - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_cpu_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_cpu_aid_present_worker); -} - -static gboolean -sysprof_cpu_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) counters = NULL; - Present *present; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_TASK (result)); - - present = g_task_get_task_data (G_TASK (result)); - - if ((counters = g_task_propagate_pointer (G_TASK (result), error))) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - g_autoptr(SysprofColorCycle) freq_cycle = sysprof_color_cycle_new (); - SysprofVisualizerGroup *usage; - SysprofVisualizerGroup *freq; - SysprofVisualizer *freq_row = NULL; - SysprofVisualizer *over_row = NULL; - gboolean found_combined = FALSE; - gboolean has_usage = FALSE; - gboolean has_freq = FALSE; - - usage = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "priority", -1000, - "title", _("CPU Usage"), - "visible", TRUE, - NULL); - - freq = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "priority", -999, - "title", _("CPU Frequency"), - "visible", TRUE, - NULL); - freq_row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", _("CPU Frequency (All)"), - "height-request", 35, - "visible", TRUE, - "y-lower", 0.0, - "y-upper", 100.0, - NULL); - sysprof_visualizer_group_insert (freq, freq_row, -1, FALSE); - - over_row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", _("CPU Usage (All)"), - "height-request", 35, - "visible", TRUE, - "y-lower", 0.0, - "y-upper", 100.0, - NULL); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *ctr = &g_array_index (counters, SysprofCaptureCounter, i); - - if (g_strcmp0 (ctr->category, "CPU Percent") == 0) - { - if (strstr (ctr->name, "Combined") != NULL) - { - GtkWidget *row; - GdkRGBA rgba; - - found_combined = TRUE; - - gdk_rgba_parse (&rgba, "#1a5fb4"); - row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - /* Translators: CPU is the processor. */ - "title", _("CPU Usage (All)"), - "height-request", 35, - "visible", TRUE, - "y-lower", 0.0, - "y-upper", 100.0, - NULL); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - rgba.alpha = 0.5; - sysprof_line_visualizer_set_fill (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - sysprof_visualizer_group_insert (usage, SYSPROF_VISUALIZER (row), 0, FALSE); - has_usage = TRUE; - } - else if (g_str_has_prefix (ctr->name, "Total CPU ")) - { - GtkWidget *row; - GdkRGBA rgba; - - sysprof_color_cycle_next (cycle, &rgba); - row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", ctr->name, - "height-request", 35, - "visible", FALSE, - "y-lower", 0.0, - "y-upper", 100.0, - NULL); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (over_row), ctr->id, &rgba); - rgba.alpha = 0.5; - sysprof_line_visualizer_set_fill (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - sysprof_visualizer_group_insert (usage, SYSPROF_VISUALIZER (row), -1, TRUE); - has_usage = TRUE; - } - } - else if (g_strcmp0 (ctr->category, "CPU Frequency") == 0) - { - if (g_str_has_prefix (ctr->name, "CPU ")) - { - g_autofree gchar *title = g_strdup_printf ("%s Frequency", ctr->name); - GtkWidget *row; - GdkRGBA rgba; - - sysprof_color_cycle_next (freq_cycle, &rgba); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (freq_row), ctr->id, &rgba); - sysprof_line_visualizer_set_dash (SYSPROF_LINE_VISUALIZER (freq_row), ctr->id, TRUE); - - row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", title, - "height-request", 35, - "visible", FALSE, - "y-lower", 0.0, - "y-upper", 100.0, - NULL); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - sysprof_line_visualizer_set_dash (SYSPROF_LINE_VISUALIZER (row), ctr->id, TRUE); - sysprof_visualizer_group_insert (freq, SYSPROF_VISUALIZER (row), -1, TRUE); - - has_freq = TRUE; - } - } - } - - if (present->has_processes) - { - GtkWidget *row; - - row = g_object_new (SYSPROF_TYPE_PROCS_VISUALIZER, - "title", _("Processes"), - "height-request", 35, - "visible", FALSE, - NULL); - sysprof_visualizer_group_insert (usage, SYSPROF_VISUALIZER (row), -1, TRUE); - } - - if (has_usage && !found_combined) - sysprof_visualizer_group_insert (usage, over_row, 0, FALSE); - else - g_object_unref (g_object_ref_sink (over_row)); - - if (has_usage) - sysprof_display_add_group (present->display, usage); - else - g_object_unref (g_object_ref_sink (usage)); - - if (has_freq) - sysprof_display_add_group (present->display, freq); - else - g_object_unref (g_object_ref_sink (freq)); - } - - return counters != NULL; -} - -static void -sysprof_cpu_aid_class_init (SysprofCpuAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_cpu_aid_prepare; - aid_class->present_async = sysprof_cpu_aid_present_async; - aid_class->present_finish = sysprof_cpu_aid_present_finish; -} - -static void -sysprof_cpu_aid_init (SysprofCpuAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("CPU Usage")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-depth-visualizer.c b/src/libsysprof-ui/sysprof-depth-visualizer.c deleted file mode 100644 index c98e46d8..00000000 --- a/src/libsysprof-ui/sysprof-depth-visualizer.c +++ /dev/null @@ -1,453 +0,0 @@ -/* sysprof-depth-visualizer.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-depth-visualizer" - -#include "config.h" - -#include - -#include "pointcache.h" -#include "sysprof-depth-visualizer.h" - -struct _SysprofDepthVisualizer -{ - SysprofVisualizer parent_instance; - SysprofCaptureReader *reader; - PointCache *points; - guint reload_source; - guint mode; - int last_width; - int last_height; - guint reloading : 1; - guint needs_reload : 1; -}; - -typedef struct -{ - SysprofCaptureReader *reader; - PointCache *pc; - gint64 begin_time; - gint64 end_time; - gint64 duration; - guint max_n_addrs; - guint mode; -} State; - -static void sysprof_depth_visualizer_reload (SysprofDepthVisualizer *self); - -G_DEFINE_TYPE (SysprofDepthVisualizer, sysprof_depth_visualizer, SYSPROF_TYPE_VISUALIZER) - -static void -state_free (State *st) -{ - g_clear_pointer (&st->reader, sysprof_capture_reader_unref); - g_clear_pointer (&st->pc, point_cache_unref); - g_slice_free (State, st); -} - -static bool -discover_max_n_addr (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - const SysprofCaptureSample *sample = (const SysprofCaptureSample *)frame; - State *st = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_SAMPLE); - g_assert (st != NULL); - - st->max_n_addrs = MAX (st->max_n_addrs, sample->n_addrs); - - return TRUE; -} - -static bool -build_point_cache_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - const SysprofCaptureSample *sample = (const SysprofCaptureSample *)frame; - State *st = user_data; - gdouble x, y; - gboolean has_kernel = FALSE; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_SAMPLE); - g_assert (st != NULL); - - x = (frame->time - st->begin_time) / (gdouble)st->duration; - y = sample->n_addrs / (gdouble)st->max_n_addrs; - - /* If this contains a context-switch (meaning we're going into the kernel - * to do some work, use a negative value for Y so that we know later on - * that we should draw it with a different color (after removing the negation - * on the value. - * - * We skip past the first index, which is always a context switch as it is - * our perf handler. - */ - for (guint i = 1; i < sample->n_addrs; i++) - { - SysprofAddressContext kind; - - if (sysprof_address_is_context_switch (sample->addrs[i], &kind)) - { - has_kernel = TRUE; - y = -y; - break; - } - } - - if (!has_kernel) - point_cache_add_point_to_set (st->pc, 1, x, y); - else - point_cache_add_point_to_set (st->pc, 2, x, y); - - return TRUE; -} - -static void -sysprof_depth_visualizer_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_SAMPLE, }; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - SysprofCaptureCondition *condition; - State *st = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_DEPTH_VISUALIZER (source_object)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - if (st->duration != 0) - { - cursor = sysprof_capture_cursor_new (st->reader); - condition = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - sysprof_capture_cursor_foreach (cursor, discover_max_n_addr, st); - sysprof_capture_cursor_reset (cursor); - sysprof_capture_cursor_foreach (cursor, build_point_cache_cb, st); - } - - g_task_return_pointer (task, - g_steal_pointer (&st->pc), - (GDestroyNotify) point_cache_unref); -} - -static void -apply_point_cache_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)object; - PointCache *pc; - - g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self)); - g_assert (G_IS_TASK (result)); - - self->reloading = FALSE; - - if ((pc = g_task_propagate_pointer (G_TASK (result), NULL))) - { - g_clear_pointer (&self->points, point_cache_unref); - self->points = g_steal_pointer (&pc); - gtk_widget_queue_draw (GTK_WIDGET (self)); - } - - if (self->needs_reload) - sysprof_depth_visualizer_reload (self); -} - -static void -sysprof_depth_visualizer_reload (SysprofDepthVisualizer *self) -{ - g_autoptr(GTask) task = NULL; - GtkAllocation alloc; - State *st; - - g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self)); - - self->needs_reload = TRUE; - - if (self->reloading) - return; - - self->reloading = TRUE; - self->needs_reload = FALSE; - - gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); - - st = g_slice_new0 (State); - st->reader = sysprof_capture_reader_ref (self->reader); - st->pc = point_cache_new (); - st->max_n_addrs = 0; - st->begin_time = sysprof_capture_reader_get_start_time (self->reader); - st->end_time = sysprof_capture_reader_get_end_time (self->reader); - st->duration = st->end_time - st->begin_time; - st->mode = self->mode; - - point_cache_add_set (st->pc, 1); - point_cache_add_set (st->pc, 2); - - task = g_task_new (self, NULL, apply_point_cache_cb, NULL); - g_task_set_source_tag (task, sysprof_depth_visualizer_reload); - g_task_set_task_data (task, st, (GDestroyNotify) state_free); - g_task_run_in_thread (task, sysprof_depth_visualizer_worker); -} - -static void -sysprof_depth_visualizer_set_reader (SysprofVisualizer *row, - SysprofCaptureReader *reader) -{ - SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)row; - - g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self)); - - if (self->reader != reader) - { - if (self->reader != NULL) - { - sysprof_capture_reader_unref (self->reader); - self->reader = NULL; - } - - if (reader != NULL) - { - self->reader = sysprof_capture_reader_ref (reader); - sysprof_depth_visualizer_reload (self); - } - } -} - -static void -sysprof_depth_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)widget; - GtkAllocation alloc; - GdkRectangle clip; - const Point *points; - cairo_t *cr; - guint n_points = 0; - GdkRGBA user; - GdkRGBA system; - - g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self)); - g_assert (snapshot != NULL); - - GTK_WIDGET_CLASS (sysprof_depth_visualizer_parent_class)->snapshot (widget, snapshot); - - if (self->points == NULL) - return; - - gdk_rgba_parse (&user, "#1a5fb4"); - gdk_rgba_parse (&system, "#3584e4"); - - gtk_widget_get_allocation (widget, &alloc); - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height)); - - /* FIXME: we should abstract visualizer drawing into regions so that we - * can still know the region we're drawing. - */ -#if 0 - if (!gdk_cairo_get_clip_rectangle (cr, &clip)) - return; -#else - clip.x = alloc.x = 0; - clip.y = alloc.y = 0; - clip.width = alloc.width; - clip.height = alloc.height; -#endif - - /* Draw user-space stacks */ - if (self->mode != SYSPROF_DEPTH_VISUALIZER_KERNEL_ONLY && - (points = point_cache_get_points (self->points, 1, &n_points))) - { - g_autofree SysprofVisualizerAbsolutePoint *out_points = NULL; - - out_points = g_new (SysprofVisualizerAbsolutePoint, n_points); - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (widget), - (const SysprofVisualizerRelativePoint *)points, - n_points, out_points, n_points); - - cairo_set_line_width (cr, 1.0); - gdk_cairo_set_source_rgba (cr, &user); - - for (guint i = 0; i < n_points; i++) - { - gdouble x, y; - - x = out_points[i].x; - y = out_points[i].y; - - if (x < clip.x) - continue; - - if (x > clip.x + clip.width) - break; - - for (guint j = i + 1; j < n_points; j++) - { - if (out_points[j].x != x) - break; - - y = MIN (y, out_points[j].y); - } - - x += alloc.x; - - cairo_move_to (cr, (guint)x + .5, alloc.height); - cairo_line_to (cr, (guint)x + .5, y); - } - - cairo_stroke (cr); - } - - /* Draw kernel-space stacks */ - if (self->mode != SYSPROF_DEPTH_VISUALIZER_USER_ONLY && - (points = point_cache_get_points (self->points, 2, &n_points))) - { - g_autofree SysprofVisualizerAbsolutePoint *out_points = NULL; - - out_points = g_new (SysprofVisualizerAbsolutePoint, n_points); - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (widget), - (const SysprofVisualizerRelativePoint *)points, - n_points, out_points, n_points); - - cairo_set_line_width (cr, 1.0); - gdk_cairo_set_source_rgba (cr, &system); - - for (guint i = 0; i < n_points; i++) - { - gdouble x, y; - - x = out_points[i].x; - y = out_points[i].y; - - if (x < clip.x) - continue; - - if (x > clip.x + clip.width) - break; - - for (guint j = i + 1; j < n_points; j++) - { - if (out_points[j].x != x) - break; - - y = MIN (y, out_points[j].y); - } - - x += alloc.x; - - cairo_move_to (cr, (guint)x + .5, alloc.height); - cairo_line_to (cr, (guint)x + .5, y); - } - - cairo_stroke (cr); - } - - cairo_destroy (cr); -} - -static gboolean -sysprof_depth_visualizer_do_reload (gpointer data) -{ - SysprofDepthVisualizer *self = data; - self->reload_source = 0; - sysprof_depth_visualizer_reload (self); - return G_SOURCE_REMOVE; -} - -static void -sysprof_depth_visualizer_queue_reload (SysprofDepthVisualizer *self) -{ - g_assert (SYSPROF_IS_DEPTH_VISUALIZER (self)); - - g_clear_handle_id (&self->reload_source, g_source_remove); - self->reload_source = g_idle_add (sysprof_depth_visualizer_do_reload, self); -} - -static void -sysprof_depth_visualizer_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)widget; - - if (width != self->last_width || height != self->last_height) - { - sysprof_depth_visualizer_queue_reload (SYSPROF_DEPTH_VISUALIZER (widget)); - self->last_width = width; - self->last_height = height; - } -} - -static void -sysprof_depth_visualizer_finalize (GObject *object) -{ - SysprofDepthVisualizer *self = (SysprofDepthVisualizer *)object; - - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - g_clear_handle_id (&self->reload_source, g_source_remove); - - G_OBJECT_CLASS (sysprof_depth_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_depth_visualizer_class_init (SysprofDepthVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofVisualizerClass *row_class = SYSPROF_VISUALIZER_CLASS (klass); - - object_class->finalize = sysprof_depth_visualizer_finalize; - - widget_class->snapshot = sysprof_depth_visualizer_snapshot; - widget_class->size_allocate = sysprof_depth_visualizer_size_allocate; - - row_class->set_reader = sysprof_depth_visualizer_set_reader; -} - -static void -sysprof_depth_visualizer_init (SysprofDepthVisualizer *self) -{ -} - -SysprofVisualizer * -sysprof_depth_visualizer_new (SysprofDepthVisualizerMode mode) -{ - SysprofDepthVisualizer *self; - - g_return_val_if_fail (mode == SYSPROF_DEPTH_VISUALIZER_COMBINED || - mode == SYSPROF_DEPTH_VISUALIZER_KERNEL_ONLY || - mode == SYSPROF_DEPTH_VISUALIZER_USER_ONLY, - NULL); - - self = g_object_new (SYSPROF_TYPE_DEPTH_VISUALIZER, NULL); - self->mode = mode; - - return SYSPROF_VISUALIZER (g_steal_pointer (&self)); -} diff --git a/src/libsysprof-ui/sysprof-details-page.c b/src/libsysprof-ui/sysprof-details-page.c deleted file mode 100644 index 0bcffa02..00000000 --- a/src/libsysprof-ui/sysprof-details-page.c +++ /dev/null @@ -1,325 +0,0 @@ -/* sysprof-details-page.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-details-page" - -#include "config.h" - -#include -#include - -#include "sysprof-mark-detail.h" -#include "sysprof-details-page.h" -#include "sysprof-ui-private.h" - -struct _SysprofDetailsPage -{ - GtkWidget parent_instance ; - - /* Template Objects */ - - GListStore *marks_store; - GtkSortListModel *mark_sort_model; - GtkLabel *counters; - GtkLabel *duration; - GtkLabel *filename; - GtkLabel *allocations; - GtkLabel *forks; - GtkLabel *marks; - GtkLabel *processes; - GtkLabel *samples; - GtkLabel *start_time; - GtkLabel *cpu_label; - - guint next_row; -}; - -G_DEFINE_TYPE (SysprofDetailsPage, sysprof_details_page, GTK_TYPE_WIDGET) - -#if GLIB_CHECK_VERSION(2, 56, 0) -# define _g_date_time_new_from_iso8601 g_date_time_new_from_iso8601 -#else -static GDateTime * -_g_date_time_new_from_iso8601 (const gchar *str, - GTimeZone *default_tz) -{ - GTimeVal tv; - - if (g_time_val_from_iso8601 (str, &tv)) - { - g_autoptr(GDateTime) dt = g_date_time_new_from_timeval_utc (&tv); - - if (default_tz) - return g_date_time_to_timezone (dt, default_tz); - else - return g_steal_pointer (&dt); - } - - return NULL; -} -#endif - -char * -format_time (GObject *unused, - gint64 time) -{ - return time ? _sysprof_format_duration (time) : g_strdup("—"); -} - -static void -sysprof_details_page_dispose (GObject *object) -{ - SysprofDetailsPage *self = (SysprofDetailsPage *)object; - GtkWidget *child; - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - gtk_widget_unparent (child); - - G_OBJECT_CLASS (sysprof_details_page_parent_class)->dispose (object); -} - -static void -sysprof_details_page_class_init (SysprofDetailsPageClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = sysprof_details_page_dispose; - - g_type_ensure (SYSPROF_TYPE_MARK_DETAIL); - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-details-page.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, allocations); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, counters); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, cpu_label); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, duration); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, filename); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, forks); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, marks); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, marks_store); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, mark_sort_model); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, processes); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, samples); - gtk_widget_class_bind_template_child (widget_class, SysprofDetailsPage, start_time); - gtk_widget_class_bind_template_callback (widget_class, format_time); -} - -static void -sysprof_details_page_init (SysprofDetailsPage *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); - - self->next_row = 8; -} - -GtkWidget * -sysprof_details_page_new (void) -{ - return g_object_new (SYSPROF_TYPE_DETAILS_PAGE, NULL); -} - -static void -update_cpu_info_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(SysprofDetailsPage) self = user_data; - g_autofree gchar *str = NULL; - - g_assert (SYSPROF_IS_DETAILS_PAGE (self)); - g_assert (G_IS_TASK (result)); - - if ((str = g_task_propagate_pointer (G_TASK (result), NULL))) - gtk_label_set_label (self->cpu_label, str); -} - -static bool -cpu_info_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - const SysprofCaptureFileChunk *fc = (gpointer)frame; - const gchar *endptr; - const gchar *line; - gchar **str = user_data; - - line = memmem ((gchar *)fc->data, fc->len, "model name", 10); - - if (!line) - return FALSE; - - endptr = (gchar *)fc->data + fc->len; - endptr = memchr (line, '\n', endptr - line); - - if (endptr) - { - gchar *tmp = *str = g_strndup (line, endptr - line); - for (; *tmp && *tmp != ':'; tmp++) - *tmp = ' '; - if (*tmp == ':') - *tmp = ' '; - g_strstrip (*str); - return FALSE; - } - - return TRUE; -} - -static void -sysprof_details_page_update_cpu_info_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofCaptureCursor *cursor = task_data; - gchar *str = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (cursor != NULL); - - sysprof_capture_cursor_foreach (cursor, cpu_info_cb, &str); - g_task_return_pointer (task, g_steal_pointer (&str), g_free); -} - -static void -sysprof_details_page_update_cpu_info (SysprofDetailsPage *self, - SysprofCaptureReader *reader) -{ - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - - g_assert (SYSPROF_IS_DETAILS_PAGE (self)); - g_assert (reader != NULL); - - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, - sysprof_capture_condition_new_where_file ("/proc/cpuinfo")); - - task = g_task_new (NULL, NULL, update_cpu_info_cb, g_object_ref (self)); - g_task_set_task_data (task, - g_steal_pointer (&cursor), - (GDestroyNotify) sysprof_capture_cursor_unref); - g_task_run_in_thread (task, sysprof_details_page_update_cpu_info_worker); -} - -void -sysprof_details_page_set_reader (SysprofDetailsPage *self, - SysprofCaptureReader *reader) -{ - g_autoptr(GDateTime) dt = NULL; - g_autoptr(GDateTime) local = NULL; - g_autofree gchar *duration_str = NULL; - const gchar *filename; - const gchar *capture_at; - SysprofCaptureStat st_buf; - gint64 duration; - - g_return_if_fail (SYSPROF_IS_DETAILS_PAGE (self)); - g_return_if_fail (reader != NULL); - - sysprof_details_page_update_cpu_info (self, reader); - - if (!(filename = sysprof_capture_reader_get_filename (reader))) - filename = _("Memory Capture"); - - gtk_label_set_label (self->filename, filename); - - if ((capture_at = sysprof_capture_reader_get_time (reader)) && - (dt = _g_date_time_new_from_iso8601 (capture_at, NULL)) && - (local = g_date_time_to_local (dt))) - { - g_autofree gchar *str = g_date_time_format (local, "%x %X"); - gtk_label_set_label (self->start_time, str); - } - - duration = sysprof_capture_reader_get_end_time (reader) - - sysprof_capture_reader_get_start_time (reader); - duration_str = g_strdup_printf (_("%0.4lf seconds"), duration / (gdouble)SYSPROF_NSEC_PER_SEC); - gtk_label_set_label (self->duration, duration_str); - - if (sysprof_capture_reader_get_stat (reader, &st_buf)) - { -#define SET_FRAME_COUNT(field, TYPE) \ - G_STMT_START { \ - g_autofree gchar *str = NULL; \ - str = g_strdup_printf ("%"G_GSIZE_FORMAT, st_buf.frame_count[TYPE]); \ - gtk_label_set_label (self->field, str); \ - } G_STMT_END - - SET_FRAME_COUNT (samples, SYSPROF_CAPTURE_FRAME_SAMPLE); - SET_FRAME_COUNT (marks, SYSPROF_CAPTURE_FRAME_MARK); - SET_FRAME_COUNT (processes, SYSPROF_CAPTURE_FRAME_PROCESS); - SET_FRAME_COUNT (forks, SYSPROF_CAPTURE_FRAME_FORK); - SET_FRAME_COUNT (counters, SYSPROF_CAPTURE_FRAME_CTRSET); - SET_FRAME_COUNT (allocations, SYSPROF_CAPTURE_FRAME_ALLOCATION); - -#undef SET_FRAME_COUNT - } -} - -void -sysprof_details_page_add_mark (SysprofDetailsPage *self, - const gchar *mark, - gint64 min, - gint64 max, - gint64 avg, - gint64 hits) -{ - SysprofMarkDetail *detail; - - g_return_if_fail (SYSPROF_IS_DETAILS_PAGE (self)); - - detail = sysprof_mark_detail_new (mark, min, max, avg, hits); - - g_list_store_append (self->marks_store, detail); - /*gtk_list_store_append (self->marks_store, &iter); - gtk_list_store_set (self->marks_store, &iter, - 0, mark, - 1, min ? _sysprof_format_duration (min) : "—", - 2, max ? _sysprof_format_duration (max) : "—", - 3, avg ? _sysprof_format_duration (avg) : "—", - 4, hits, - 5, detail, - -1);*/ - g_object_unref (detail); -} - -void -sysprof_details_page_add_marks (SysprofDetailsPage *self, - const SysprofMarkStat *marks, - guint n_marks) -{ - g_return_if_fail (SYSPROF_IS_DETAILS_PAGE (self)); - g_return_if_fail (marks != NULL || n_marks == 0); - - if (marks == NULL || n_marks == 0) - return; - - /* Be reasonable */ - if (n_marks > 100) - n_marks = 100; - - for (guint i = 0; i < n_marks; i++) - sysprof_details_page_add_mark (self, - marks[i].name, - marks[i].min, - marks[i].max, - marks[i].avg, - marks[i].count); -} diff --git a/src/libsysprof-ui/sysprof-details-page.h b/src/libsysprof-ui/sysprof-details-page.h deleted file mode 100644 index 63b919fc..00000000 --- a/src/libsysprof-ui/sysprof-details-page.h +++ /dev/null @@ -1,57 +0,0 @@ -/* sysprof-details-page.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -G_BEGIN_DECLS - -SYSPROF_ALIGNED_BEGIN (8) -typedef struct -{ - gchar name[152]; - guint64 count; - gint64 max; - gint64 min; - gint64 avg; - guint64 avg_count; -} SysprofMarkStat -SYSPROF_ALIGNED_END (8); - -#define SYSPROF_TYPE_DETAILS_PAGE (sysprof_details_page_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofDetailsPage, sysprof_details_page, SYSPROF, DETAILS_PAGE, GtkWidget) - -GtkWidget *sysprof_details_page_new (void); -void sysprof_details_page_set_reader (SysprofDetailsPage *self, - SysprofCaptureReader *reader); -void sysprof_details_page_add_marks (SysprofDetailsPage *self, - const SysprofMarkStat *marks, - guint n_marks); -void sysprof_details_page_add_mark (SysprofDetailsPage *self, - const gchar *mark, - gint64 min, - gint64 max, - gint64 avg, - gint64 hits); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-details-page.ui b/src/libsysprof-ui/sysprof-details-page.ui deleted file mode 100644 index e9de42de..00000000 --- a/src/libsysprof-ui/sysprof-details-page.ui +++ /dev/null @@ -1,361 +0,0 @@ - - - - - marks_store - true - - marks_view - - - - - diff --git a/src/libsysprof-ui/sysprof-diskstat-aid.c b/src/libsysprof-ui/sysprof-diskstat-aid.c deleted file mode 100644 index 676ea4a3..00000000 --- a/src/libsysprof-ui/sysprof-diskstat-aid.c +++ /dev/null @@ -1,269 +0,0 @@ -/* sysprof-diskstat-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-diskstat-aid" - -#include "config.h" - -#include -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-duplex-visualizer.h" -#include "sysprof-diskstat-aid.h" - -struct _SysprofDiskstatAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; -} Present; - -G_DEFINE_TYPE (SysprofDiskstatAid, sysprof_diskstat_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -/** - * sysprof_diskstat_aid_new: - * - * Create a new #SysprofDiskstatAid. - * - * Returns: (transfer full): a newly created #SysprofDiskstatAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_diskstat_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_DISKSTAT_AID, NULL); -} - -static void -sysprof_diskstat_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_DISKSTAT_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_diskstat_source_new (); - sysprof_profiler_add_source (profiler, source); -} - -static bool -collect_diskstat_counters (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - GArray *counters = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (counters != NULL); - - for (guint i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *counter = &def->counters[i]; - - if (strcmp (counter->category, "Disk") == 0 && - (g_str_has_prefix (counter->name, "Total Reads") || - g_str_has_prefix (counter->name, "Total Writes"))) - g_array_append_vals (counters, counter, 1); - } - - return TRUE; -} - -static void -sysprof_diskstat_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *present = task_data; - g_autoptr(GArray) counters = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_DISKSTAT_AID (source_object)); - g_assert (present != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - sysprof_capture_cursor_foreach (present->cursor, collect_diskstat_counters, counters); - g_task_return_pointer (task, - g_steal_pointer (&counters), - (GDestroyNotify) g_array_unref); -} - -static guint -find_other_id (GArray *counters, - const gchar *name) -{ - g_autofree gchar *other = NULL; - - g_assert (counters); - g_assert (name != NULL); - g_assert (g_str_has_prefix (name, "Total Reads")); - - other = g_strdup_printf ("Total Writes%s", name + strlen ("Total Reads")); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *c = &g_array_index (counters, SysprofCaptureCounter, i); - - if (g_str_equal (c->name, other)) - return c->id; - } - - return 0; -} - -static void -sysprof_diskstat_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_CTRDEF }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_DISKSTAT_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (1, types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_diskstat_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_diskstat_aid_present_worker); -} - -static gboolean -sysprof_diskstat_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) counters = NULL; - Present *present; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_TASK (result)); - - present = g_task_get_task_data (G_TASK (result)); - - if ((counters = g_task_propagate_pointer (G_TASK (result), error))) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - SysprofVisualizerGroup *group; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "title", _("Disk"), - "visible", TRUE, - NULL); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *ctr = &g_array_index (counters, SysprofCaptureCounter, i); - - if (g_str_has_prefix (ctr->name, "Total Reads")) - { - g_autofree gchar *title = NULL; - gboolean is_combined; - GtkWidget *row; - GdkRGBA rgba; - guint other_id; - - if (!(other_id = find_other_id (counters, ctr->name))) - continue; - - is_combined = g_str_equal (ctr->description, "Combined"); - - if (is_combined) - title = g_strdup ("Disk Reads/Writes (All)"); - else - title = g_strdup_printf ("Disk Reads/Writes%s", ctr->name + strlen ("Total Reads")); - - row = g_object_new (SYSPROF_TYPE_DUPLEX_VISUALIZER, - "title", title, - "height-request", 35, - "visible", is_combined, - NULL); - sysprof_color_cycle_next (cycle, &rgba); - sysprof_duplex_visualizer_set_counters (SYSPROF_DUPLEX_VISUALIZER (row), ctr->id, other_id); - sysprof_duplex_visualizer_set_colors (SYSPROF_DUPLEX_VISUALIZER (row), &rgba, &rgba); - sysprof_duplex_visualizer_set_labels (SYSPROF_DUPLEX_VISUALIZER (row), _("Reads"), _("Writes")); - sysprof_duplex_visualizer_set_use_diff (SYSPROF_DUPLEX_VISUALIZER (row), FALSE); - sysprof_visualizer_group_insert (group, SYSPROF_VISUALIZER (row), is_combined ? 0 : -1, !is_combined); - } - } - - if (counters->len > 0) - sysprof_display_add_group (present->display, group); - else - g_object_unref (g_object_ref_sink (group)); - } - - return counters != NULL; -} - -static void -sysprof_diskstat_aid_class_init (SysprofDiskstatAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_diskstat_aid_prepare; - aid_class->present_async = sysprof_diskstat_aid_present_async; - aid_class->present_finish = sysprof_diskstat_aid_present_finish; -} - -static void -sysprof_diskstat_aid_init (SysprofDiskstatAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Disk")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "drive-harddisk-system-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-diskstat-aid.h b/src/libsysprof-ui/sysprof-diskstat-aid.h deleted file mode 100644 index c2938a41..00000000 --- a/src/libsysprof-ui/sysprof-diskstat-aid.h +++ /dev/null @@ -1,33 +0,0 @@ -/* sysprof-diskstat-aid.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-aid.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_DISKSTAT_AID (sysprof_diskstat_aid_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofDiskstatAid, sysprof_diskstat_aid, SYSPROF, DISKSTAT_AID, SysprofAid) - -SysprofAid *sysprof_diskstat_aid_new (void); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-display.c b/src/libsysprof-ui/sysprof-display.c deleted file mode 100644 index 0c0ac27c..00000000 --- a/src/libsysprof-ui/sysprof-display.c +++ /dev/null @@ -1,1340 +0,0 @@ -/* sysprof-display.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-display" - -#include "config.h" - -#include - -#include "egg-paned-private.h" - -#include "sysprof-details-page.h" -#include "sysprof-display-private.h" -#include "sysprof-profiler-assistant.h" -#include "sysprof-failed-state-view.h" -#include "sysprof-recording-state-view.h" -#include "sysprof-theme-manager.h" -#include "sysprof-ui-private.h" -#include "sysprof-visualizers-frame.h" -#include "sysprof-visualizer-group-private.h" - -#include "sysprof-battery-aid.h" -#include "sysprof-callgraph-aid.h" -#include "sysprof-counters-aid.h" -#include "sysprof-cpu-aid.h" -#include "sysprof-diskstat-aid.h" -#include "sysprof-logs-aid.h" -#include "sysprof-marks-aid.h" -#include "sysprof-memory-aid.h" -#include "sysprof-memprof-aid.h" -#include "sysprof-netdev-aid.h" -#include "sysprof-rapl-aid.h" - -typedef enum -{ - SYSPROF_CAPTURE_FLAGS_CAN_REPLAY = 1 << 1, -} SysprofCaptureFlags; - -typedef struct -{ - SysprofCaptureReader *reader; - SysprofCaptureCondition *filter; - GFile *file; - SysprofProfiler *profiler; - GError *error; - - /* Template Widgets */ - SysprofVisualizersFrame *visualizers; - GtkStack *pages; - SysprofDetailsPage *details; - GtkStack *stack; - SysprofProfilerAssistant *assistant; - SysprofRecordingStateView *recording_view; - SysprofFailedStateView *failed_view; - - SysprofCaptureFlags flags; -} SysprofDisplayPrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofDisplay, sysprof_display, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_CAN_REPLAY, - PROP_CAN_SAVE, - PROP_RECORDING, - PROP_TITLE, - PROP_VISIBLE_PAGE, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -update_title_child_property (SysprofDisplay *self) -{ - GtkWidget *parent; - - g_assert (SYSPROF_IS_DISPLAY (self)); - - if ((parent = gtk_widget_get_parent (GTK_WIDGET (self))) && GTK_IS_NOTEBOOK (parent)) - { - g_autofree gchar *title = sysprof_display_dup_title (self); - gtk_notebook_set_menu_label_text (GTK_NOTEBOOK (parent), GTK_WIDGET (self), title); - } - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); -} - -static void -sysprof_display_profiler_failed_cb (SysprofDisplay *self, - const GError *error, - SysprofProfiler *profiler) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (error != NULL); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - g_clear_object (&priv->profiler); - - /* Save the error for future use */ - g_clear_error (&priv->error); - priv->error = g_error_copy (error); - - gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (priv->failed_view)); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RECORDING]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_REPLAY]); -} - -static void -sysprof_display_profiler_stopped_cb (SysprofDisplay *self, - SysprofProfiler *profiler) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - SysprofCaptureWriter *writer; - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - if ((writer = sysprof_profiler_get_writer (profiler))) - { - g_autoptr(SysprofCaptureReader) reader = NULL; - g_autoptr(GError) error = NULL; - - if (!(reader = sysprof_capture_writer_create_reader_with_error (writer, &error))) - { - g_warning ("Failed to create capture creader: %s\n", error->message); - gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (priv->failed_view)); - goto notify; - } - - sysprof_display_load_async (self, - reader, - NULL, NULL, NULL); - gtk_stack_set_visible_child_name (priv->stack, "view"); - } - -notify: - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_REPLAY]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_SAVE]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RECORDING]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); -} - -static void -sysprof_display_set_profiler (SysprofDisplay *self, - SysprofProfiler *profiler) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - if (g_set_object (&priv->profiler, profiler)) - { - sysprof_recording_state_view_set_profiler (priv->recording_view, profiler); - gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (priv->recording_view)); - - g_signal_connect_object (profiler, - "stopped", - G_CALLBACK (sysprof_display_profiler_stopped_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (profiler, - "failed", - G_CALLBACK (sysprof_display_profiler_failed_cb), - self, - G_CONNECT_SWAPPED); - } - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RECORDING]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); -} - -static void -sysprof_display_start_recording_cb (SysprofDisplay *self, - SysprofProfiler *profiler, - SysprofProfilerAssistant *assistant) -{ - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - g_assert (!assistant || SYSPROF_IS_PROFILER_ASSISTANT (assistant)); - g_assert (sysprof_display_is_empty (self)); - - sysprof_display_set_profiler (self, profiler); - sysprof_profiler_start (profiler); -} - -static gboolean -sysprof_display_get_is_recording (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_assert (SYSPROF_IS_DISPLAY (self)); - - return GTK_WIDGET (priv->recording_view) == gtk_stack_get_visible_child (priv->stack); -} - -gchar * -sysprof_display_dup_title (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), NULL); - - if (priv->error) - return g_strdup (_("Recording Failed")); - - if (priv->profiler != NULL) - { - if (sysprof_profiler_get_is_running (priv->profiler)) - return g_strdup (_("Recording…")); - } - - if (priv->file != NULL) - return g_file_get_basename (priv->file); - - if (priv->reader != NULL) - { - g_autoptr(GDateTime) dt = NULL; - const gchar *filename; - const gchar *capture_time; - - if ((filename = sysprof_capture_reader_get_filename (priv->reader))) - return g_path_get_basename (filename); - - /* This recording is not yet on disk, but the time it was recorded - * is much more useful than "New Recording". - */ - capture_time = sysprof_capture_reader_get_time (priv->reader); - - if ((dt = g_date_time_new_from_iso8601 (capture_time, NULL))) - { - g_autoptr(GDateTime) local = g_date_time_to_local (dt); - g_autofree gchar *formatted = NULL; - - if (local != NULL) - formatted = g_date_time_format (local, "%X"); - else - formatted = g_date_time_format (dt, "%X"); - - /* translators: %s is replaced with locale specific time of recording */ - return g_strdup_printf (_("Recording at %s"), formatted); - } - } - - return g_strdup (_("New Recording")); -} - -/** - * sysprof_display_new: - * - * Create a new #SysprofDisplay. - * - * Returns: (transfer full): a newly created #SysprofDisplay - */ -GtkWidget * -sysprof_display_new (void) -{ - return g_object_new (SYSPROF_TYPE_DISPLAY, NULL); -} - -static void -sysprof_display_notify_selection_cb (SysprofDisplay *self, - GParamSpec *pspec, - SysprofVisualizersFrame *visualizers) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - SysprofSelection *selection; - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (visualizers)); - - g_clear_pointer (&priv->filter, sysprof_capture_condition_unref); - - if ((selection = sysprof_visualizers_frame_get_selection (visualizers))) - { - SysprofCaptureCondition *cond = NULL; - guint n_ranges = sysprof_selection_get_n_ranges (selection); - - for (guint i = 0; i < n_ranges; i++) - { - SysprofCaptureCondition *c; - gint64 begin, end; - - sysprof_selection_get_nth_range (selection, i, &begin, &end); - c = sysprof_capture_condition_new_where_time_between (begin, end); - - if (cond == NULL) - cond = c; - else - cond = sysprof_capture_condition_new_or (cond, c); - } - - priv->filter = cond; - - /* Opportunistically load pages */ - if (priv->reader != NULL) - { - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (priv->pages)); - child; - child = gtk_widget_get_next_sibling (child)) - { - if (SYSPROF_IS_PAGE (child)) - sysprof_page_load_async (SYSPROF_PAGE (child), - priv->reader, - selection, - priv->filter, - NULL, NULL, NULL); - } - } - } -} - -static void -change_page_cb (GSimpleAction *action, - GVariant *param, - gpointer user_data) -{ - SysprofDisplay *self = user_data; - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_assert (G_IS_SIMPLE_ACTION (action)); - g_assert (param != NULL); - - if (g_variant_is_of_type (param, G_VARIANT_TYPE_STRING)) - { - const gchar *str = g_variant_get_string (param, NULL); - - gtk_stack_set_visible_child_name (priv->pages, str); - - if (g_str_equal (str, "details")) - sysprof_visualizers_frame_unselect_row (priv->visualizers); - } -} - -static void -stop_recording_cb (GSimpleAction *action, - GVariant *param, - gpointer user_data) -{ - SysprofDisplay *self = user_data; - - g_assert (G_IS_SIMPLE_ACTION (action)); - g_assert (SYSPROF_IS_DISPLAY (self)); - - sysprof_display_stop_recording (self); -} - -static void -sysprof_display_dispose (GObject *object) -{ - SysprofDisplay *self = (SysprofDisplay *)object; - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - if (priv->stack) - { - gtk_widget_unparent (GTK_WIDGET (priv->stack)); - priv->stack = NULL; - } - - g_clear_pointer (&priv->reader, sysprof_capture_reader_unref); - g_clear_pointer (&priv->filter, sysprof_capture_condition_unref); - - G_OBJECT_CLASS (sysprof_display_parent_class)->dispose (object); -} - -static void -sysprof_display_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofDisplay *self = SYSPROF_DISPLAY (object); - - switch (prop_id) - { - case PROP_CAN_REPLAY: - g_value_set_boolean (value, sysprof_display_get_can_replay (self)); - break; - - case PROP_CAN_SAVE: - g_value_set_boolean (value, sysprof_display_get_can_save (self)); - break; - - case PROP_RECORDING: - g_value_set_boolean (value, sysprof_display_get_is_recording (self)); - break; - - case PROP_TITLE: - g_value_take_string (value, sysprof_display_dup_title (self)); - break; - - case PROP_VISIBLE_PAGE: - g_value_set_object (value, sysprof_display_get_visible_page (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_display_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofDisplay *self = SYSPROF_DISPLAY (object); - - switch (prop_id) - { - case PROP_VISIBLE_PAGE: - sysprof_display_set_visible_page (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_display_class_init (SysprofDisplayClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_display_dispose; - object_class->get_property = sysprof_display_get_property; - object_class->set_property = sysprof_display_set_property; - - sysprof_theme_manager_register_resource (sysprof_theme_manager_get_default (), - NULL, - NULL, - "/org/gnome/sysprof/css/SysprofDisplay-shared.css"); - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-display.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_set_css_name (widget_class, "SysprofDisplay"); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, assistant); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, details); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, failed_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, pages); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, recording_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, stack); - gtk_widget_class_bind_template_child_private (widget_class, SysprofDisplay, visualizers); - - properties [PROP_CAN_REPLAY] = - g_param_spec_boolean ("can-replay", - "Can Replay", - "If the capture contains enough information to re-run the recording", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CAN_SAVE] = - g_param_spec_boolean ("can-save", - "Can Save", - "If the display can save a recording", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_RECORDING] = - g_param_spec_boolean ("recording", - "Recording", - "If the display is in recording state", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_TITLE] = - g_param_spec_string ("title", - "Title", - "The title of the display", - NULL, - (G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_VISIBLE_PAGE] = - g_param_spec_object ("visible-page", - "Visible Page", - "Visible Page", - SYSPROF_TYPE_PAGE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - g_type_ensure (EGG_TYPE_PANED); - g_type_ensure (SYSPROF_TYPE_DETAILS_PAGE); - g_type_ensure (SYSPROF_TYPE_FAILED_STATE_VIEW); - g_type_ensure (SYSPROF_TYPE_PROFILER_ASSISTANT); - g_type_ensure (SYSPROF_TYPE_RECORDING_STATE_VIEW); - g_type_ensure (SYSPROF_TYPE_VISUALIZERS_FRAME); -} - -static void -sysprof_display_init (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - g_autoptr(GSimpleActionGroup) group = g_simple_action_group_new (); - static GActionEntry entries[] = { - { "page", change_page_cb, "s" }, - { "stop-recording", stop_recording_cb }, - }; - g_autoptr(GPropertyAction) page = NULL; - - gtk_widget_init_template (GTK_WIDGET (self)); - - g_signal_connect_object (priv->assistant, - "start-recording", - G_CALLBACK (sysprof_display_start_recording_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->visualizers, - "notify::selection", - G_CALLBACK (sysprof_display_notify_selection_cb), - self, - G_CONNECT_SWAPPED); - - page = g_property_action_new ("page", priv->pages, "visible-child-name"); - g_action_map_add_action_entries (G_ACTION_MAP (group), - entries, - G_N_ELEMENTS (entries), - self); - gtk_widget_insert_action_group (GTK_WIDGET (self), "display", G_ACTION_GROUP (group)); -} - -void -sysprof_display_add_group (SysprofDisplay *self, - SysprofVisualizerGroup *group) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (group)); - - if (priv->reader != NULL) - _sysprof_visualizer_group_set_reader (group, priv->reader); - - sysprof_visualizers_frame_add_group (priv->visualizers, group); -} - -void -sysprof_display_add_page (SysprofDisplay *self, - SysprofPage *page) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - SysprofSelection *selection; - const gchar *title; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (SYSPROF_IS_PAGE (page)); - - title = sysprof_page_get_title (page); - gtk_stack_add_titled (priv->pages, GTK_WIDGET (page), NULL, title); - - selection = sysprof_visualizers_frame_get_selection (priv->visualizers); - - sysprof_page_set_size_group (page, - sysprof_visualizers_frame_get_size_group (priv->visualizers)); - - sysprof_page_set_hadjustment (page, - sysprof_visualizers_frame_get_hadjustment (priv->visualizers)); - - if (priv->reader != NULL) - sysprof_page_load_async (page, - priv->reader, - selection, - priv->filter, - NULL, NULL, NULL); -} - -SysprofPage * -sysprof_display_get_visible_page (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - GtkWidget *visible_page; - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), NULL); - - visible_page = gtk_stack_get_visible_child (priv->pages); - - if (SYSPROF_IS_PAGE (visible_page)) - return SYSPROF_PAGE (visible_page); - - return NULL; -} - -void -sysprof_display_set_visible_page (SysprofDisplay *self, - SysprofPage *page) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (SYSPROF_IS_PAGE (page)); - - gtk_stack_set_visible_child (priv->pages, GTK_WIDGET (page)); -} - -static void -sysprof_display_present_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofAid *aid = (SysprofAid *)object; - g_autoptr(GTask) task = user_data; - g_autoptr(GError) error = NULL; - gatomicrefcount *n_active; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!sysprof_aid_present_finish (aid, result, &error)) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) - g_warning ("Failed to present aid %s: %s", G_OBJECT_TYPE_NAME (aid), error->message); - } - - n_active = g_task_get_task_data (task); - - if (g_atomic_ref_count_dec (n_active)) - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_display_present_async (SysprofDisplay *self, - SysprofCaptureReader *reader, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GPtrArray) aids = NULL; - g_autoptr(GTask) task = NULL; - g_autofree gatomicrefcount *aids_len = NULL; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - aids = g_ptr_array_new_with_free_func (g_object_unref); - g_ptr_array_add (aids, sysprof_battery_aid_new ()); - g_ptr_array_add (aids, sysprof_counters_aid_new ()); - g_ptr_array_add (aids, sysprof_cpu_aid_new ()); - g_ptr_array_add (aids, sysprof_callgraph_aid_new ()); - g_ptr_array_add (aids, sysprof_diskstat_aid_new ()); - g_ptr_array_add (aids, sysprof_logs_aid_new ()); - g_ptr_array_add (aids, sysprof_marks_aid_new ()); - g_ptr_array_add (aids, sysprof_memory_aid_new ()); - g_ptr_array_add (aids, sysprof_memprof_aid_new ()); - g_ptr_array_add (aids, sysprof_netdev_aid_new ()); - g_ptr_array_add (aids, sysprof_rapl_aid_new ()); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_display_present_async); - - if (aids->len == 0) - { - g_task_return_boolean (task, TRUE); - return; - } - - aids_len = g_new (gatomicrefcount, 1); - g_atomic_ref_count_init(aids_len); - *aids_len = aids->len; - - g_task_set_task_data (task, g_steal_pointer (&aids_len), g_free); - - for (guint i = 0; i < aids->len; i++) - { - SysprofAid *aid = g_ptr_array_index (aids, i); - - sysprof_aid_present_async (aid, - reader, - self, - cancellable, - sysprof_display_present_cb, - g_object_ref (task)); - } -} - -static gboolean -sysprof_display_present_finish (SysprofDisplay *self, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_display_scan_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofDisplay *self = source_object; - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - SysprofCaptureReader *reader = task_data; - g_autoptr(GHashTable) mark_stats = NULL; - g_autoptr(GArray) marks = NULL; - SysprofCaptureFrame frame; - SysprofCaptureStat st = {{0}}; - SysprofCaptureFlags flags = 0; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (reader != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - /* Scan the reader until the end so that we know we have gotten - * all of the timing data loaded into the underlying reader. - */ - - mark_stats = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - marks = g_array_new (FALSE, FALSE, sizeof (SysprofMarkStat)); - - while (sysprof_capture_reader_peek_frame (reader, &frame)) - { - if (frame.type < G_N_ELEMENTS (st.frame_count)) - st.frame_count[frame.type]++; - - if (frame.type == SYSPROF_CAPTURE_FRAME_METADATA) - { - const SysprofCaptureMetadata *meta; - - if ((meta = sysprof_capture_reader_read_metadata (reader))) - { - if (g_strcmp0 (meta->id, "local-profiler") == 0) - flags |= SYSPROF_CAPTURE_FLAGS_CAN_REPLAY; - } - } - else if (frame.type == SYSPROF_CAPTURE_FRAME_MARK) - { - const SysprofCaptureMark *mark; - - if ((mark = sysprof_capture_reader_read_mark (reader))) - { - SysprofMarkStat *mstat; - gchar name[152]; - gpointer idx; - - g_snprintf (name, sizeof name, "%s:%s", mark->group, mark->name); - - if (!(idx = g_hash_table_lookup (mark_stats, name))) - { - SysprofMarkStat empty = {{0}}; - - g_strlcpy (empty.name, name, sizeof empty.name); - g_array_append_val (marks, empty); - idx = GUINT_TO_POINTER (marks->len); - g_hash_table_insert (mark_stats, g_strdup (name), idx); - } - - mstat = &g_array_index (marks, SysprofMarkStat, GPOINTER_TO_UINT (idx) - 1); - - if (mark->duration > 0) - { - if (mstat->min == 0 || mark->duration < mstat->min) - mstat->min = mark->duration; - } - - if (mark->duration > mstat->max) - mstat->max = mark->duration; - - if (mark->duration > 0) - { - mstat->avg += mark->duration; - mstat->avg_count++; - } - - mstat->count++; - } - } - else - { - sysprof_capture_reader_skip (reader); - } - } - - { - GHashTableIter iter; - gpointer k,v; - - g_hash_table_iter_init (&iter, mark_stats); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - guint idx = GPOINTER_TO_UINT (v) - 1; - SysprofMarkStat *mstat = &g_array_index (marks, SysprofMarkStat, idx); - - if (mstat->avg_count > 0 && mstat->avg > 0) - mstat->avg /= mstat->avg_count; - -#if 0 - g_print ("%s: count=%ld avg=%ld min=%ld max=%ld\n", - (gchar*)k, - ((SysprofMarkStat *)v)->count, - ((SysprofMarkStat *)v)->avg, - ((SysprofMarkStat *)v)->min, - ((SysprofMarkStat *)v)->max); -#endif - } - } - - g_object_set_data_full (G_OBJECT (task), - "MARK_STAT", - g_steal_pointer (&marks), - (GDestroyNotify) g_array_unref); - - g_atomic_int_set (&priv->flags, flags); - sysprof_capture_reader_reset (reader); - sysprof_capture_reader_set_stat (reader, &st); - - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_display_scan_async (SysprofDisplay *self, - SysprofCaptureReader *reader, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_display_scan_async); - g_task_set_task_data (task, - sysprof_capture_reader_ref (reader), - (GDestroyNotify) sysprof_capture_reader_unref); - g_task_run_in_thread (task, sysprof_display_scan_worker); -} - -static gboolean -sysprof_display_scan_finish (SysprofDisplay *self, - GAsyncResult *result, - GError **error) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - GArray *marks; - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - if ((marks = g_object_get_data (G_OBJECT (result), "MARK_STAT"))) - sysprof_details_page_add_marks (priv->details, - (const SysprofMarkStat *)(gpointer)marks->data, - marks->len); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_REPLAY]); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_display_load_present_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofDisplay *self = (SysprofDisplay *)object; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!sysprof_display_present_finish (self, result, &error)) - g_warning ("Error presenting: %s", error->message); - - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_display_load_frame_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofVisualizersFrame *frame = (SysprofVisualizersFrame *)object; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - SysprofCaptureReader *reader; - SysprofDisplay *self; - GCancellable *cancellable; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (frame)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - self = g_task_get_source_object (task); - reader = g_task_get_task_data (task); - cancellable = g_task_get_cancellable (task); - - if (!sysprof_visualizers_frame_load_finish (frame, result, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - sysprof_display_present_async (self, - reader, - cancellable, - sysprof_display_load_present_cb, - g_steal_pointer (&task)); -} - -static void -sysprof_display_load_scan_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofDisplay *self = (SysprofDisplay *)object; - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - g_autoptr(GTask) task = user_data; - g_autoptr(GError) error = NULL; - SysprofCaptureReader *reader; - SysprofSelection *selection; - GCancellable *cancellable; - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - reader = g_task_get_task_data (task); - cancellable = g_task_get_cancellable (task); - - if (!sysprof_display_scan_finish (self, result, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - sysprof_visualizers_frame_load_async (priv->visualizers, - reader, - cancellable, - sysprof_display_load_frame_cb, - g_steal_pointer (&task)); - - selection = sysprof_visualizers_frame_get_selection (priv->visualizers); - - sysprof_details_page_set_reader (priv->details, reader); - - /* Opportunistically load pages */ - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (priv->pages)); - child; - child = gtk_widget_get_next_sibling (child)) - { - if (SYSPROF_IS_PAGE (child)) - sysprof_page_load_async (SYSPROF_PAGE (child), - reader, - selection, - priv->filter, - NULL, NULL, NULL); - } - - gtk_stack_set_visible_child_name (priv->stack, "view"); -} - -void -sysprof_display_load_async (SysprofDisplay *self, - SysprofCaptureReader *reader, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - if (priv->reader != reader) - { - g_clear_pointer (&priv->reader, sysprof_capture_reader_unref); - priv->reader = sysprof_capture_reader_ref (reader); - } - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_display_load_async); - g_task_set_task_data (task, - sysprof_capture_reader_ref (reader), - (GDestroyNotify) sysprof_capture_reader_unref); - - /* First scan the reader for any sort of data we care about before - * we notify aids to load content. That allows us to ensure we have - * proper timing data for the consumers. - */ - sysprof_display_scan_async (self, - reader, - cancellable, - sysprof_display_load_scan_cb, - g_steal_pointer (&task)); -} - -gboolean -sysprof_display_load_finish (SysprofDisplay *self, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -SysprofZoomManager * -sysprof_display_get_zoom_manager (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), NULL); - - return sysprof_visualizers_frame_get_zoom_manager (priv->visualizers); -} - -/** - * sysprof_display_is_empty: - * - * Checks if any content is or will be loaded into @self. - * - * Returns: %TRUE if the tab is unperterbed. - * - * Since: 3.34 - */ -gboolean -sysprof_display_is_empty (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), FALSE); - - return priv->file == NULL && - priv->profiler == NULL && - gtk_stack_get_visible_child (priv->stack) == GTK_WIDGET (priv->assistant) && - NULL == priv->reader; -} - -void -sysprof_display_open (SysprofDisplay *self, - GFile *file) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - g_autoptr(SysprofCaptureReader) reader = NULL; - g_autoptr(GError) error = NULL; - g_autofree gchar *path = NULL; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (G_IS_FILE (file)); - g_return_if_fail (g_file_is_native (file)); - g_return_if_fail (sysprof_display_is_empty (self)); - - path = g_file_get_path (file); - - /* If the file is executable, just set the path to the binary - * in the profiler assistant. - */ - if (g_file_test (path, G_FILE_TEST_IS_EXECUTABLE)) - { - sysprof_profiler_assistant_set_executable (priv->assistant, path); - return; - } - - g_set_object (&priv->file, file); - - if (!(reader = sysprof_capture_reader_new_with_error (path, &error))) - { - GtkWidget *dialog; - GtkWidget *window; - - g_warning ("Failed to open capture: %s", error->message); - - window = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_WINDOW); - dialog = gtk_message_dialog_new (NULL, - GTK_DIALOG_MODAL, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CLOSE, - "%s", - _("The recording could not be opened")); - gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), - "%s", - error->message); - g_signal_connect (dialog, - "response", - G_CALLBACK (gtk_window_destroy), - NULL); - gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (window)); - gtk_window_present (GTK_WINDOW (dialog)); - - _sysprof_display_destroy (self); - - return; - } - - /* Jump right to the "view" page to avoid a quick view of the - * assistants page when loading. - */ - if (g_strcmp0 ("assistant", gtk_stack_get_visible_child_name (priv->stack)) == 0) - gtk_stack_set_visible_child_name (priv->stack, "view"); - - sysprof_display_load_async (self, reader, NULL, NULL, NULL); - update_title_child_property (self); -} - -gboolean -sysprof_display_get_can_save (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), FALSE); - - return priv->reader != NULL; -} - -void -sysprof_display_stop_recording (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - - if (priv->profiler != NULL) - sysprof_profiler_stop (priv->profiler); -} - -void -_sysprof_display_focus_record (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - - _sysprof_profiler_assistant_focus_record (priv->assistant); -} - -gboolean -sysprof_display_get_can_replay (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), FALSE); - - return !sysprof_display_is_empty (self) && - priv->reader != NULL && - !!(priv->flags & SYSPROF_CAPTURE_FLAGS_CAN_REPLAY); -} - -/** - * sysprof_display_replay: - * @self: a #SysprofDisplay - * - * Gets a new display that will re-run the previous recording that is being - * viewed. This requires a capture file which contains enough information to - * replay the operation. - * - * Returns: (nullable) (transfer full): a #SysprofDisplay or %NULL - * - * Since: 3.34 - */ -SysprofDisplay * -sysprof_display_replay (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - g_autoptr(SysprofProfiler) profiler = NULL; - SysprofDisplay *copy; - - g_return_val_if_fail (SYSPROF_IS_DISPLAY (self), NULL); - g_return_val_if_fail (priv->reader != NULL, NULL); - - profiler = sysprof_local_profiler_new_replay (priv->reader); - g_return_val_if_fail (profiler != NULL, NULL); - g_return_val_if_fail (SYSPROF_IS_LOCAL_PROFILER (profiler), NULL); - - copy = g_object_new (SYSPROF_TYPE_DISPLAY, NULL); - sysprof_display_set_profiler (copy, profiler); - sysprof_profiler_start (profiler); - - return g_steal_pointer (©); -} - -GtkWidget * -sysprof_display_new_for_profiler (SysprofProfiler *profiler) -{ - SysprofDisplay *self; - - g_return_val_if_fail (SYSPROF_IS_PROFILER (profiler), NULL); - - self = g_object_new (SYSPROF_TYPE_DISPLAY, NULL); - sysprof_display_set_profiler (self, profiler); - - return GTK_WIDGET (g_steal_pointer (&self)); -} - -static void -on_save_response_cb (SysprofDisplay *self, - int res, - GtkFileChooserNative *chooser) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - g_autoptr(GFile) file = NULL; - - g_assert (SYSPROF_IS_DISPLAY (self)); - g_assert (GTK_IS_FILE_CHOOSER_NATIVE (chooser)); - - switch (res) - { - case GTK_RESPONSE_ACCEPT: - file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (chooser)); - - if (g_file_is_native (file)) - { - g_autofree gchar *path = g_file_get_path (file); - g_autoptr(GError) error = NULL; - - if (!sysprof_capture_reader_save_as_with_error (priv->reader, path, &error)) - { - GtkWidget *msg; - GtkNative *root; - - root = gtk_widget_get_native (GTK_WIDGET (self)); - msg = gtk_message_dialog_new (GTK_WINDOW (root), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, - _("Failed to save recording: %s"), - error->message); - gtk_window_present (GTK_WINDOW (msg)); - g_signal_connect (msg, "response", G_CALLBACK (gtk_window_destroy), NULL); - } - } - else - { - g_autofree char *uri = g_file_get_uri (file); - g_warning ("%s is not native, cannot open", uri); - } - - break; - - default: - break; - } - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); - gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (chooser)); -} - -void -sysprof_display_save (SysprofDisplay *self) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - GtkFileChooserNative *native; - GtkNative *root; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (priv->reader != NULL); - - root = gtk_widget_get_native (GTK_WIDGET (self)); - native = gtk_file_chooser_native_new (_("Save Recording"), - GTK_WINDOW (root), - GTK_FILE_CHOOSER_ACTION_SAVE, - _("Save"), - _("Cancel")); - gtk_file_chooser_set_create_folders (GTK_FILE_CHOOSER (native), TRUE); - gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (native), "capture.syscap"); - - g_signal_connect_object (native, - "response", - G_CALLBACK (on_save_response_cb), - self, - G_CONNECT_SWAPPED); - - gtk_native_dialog_show (GTK_NATIVE_DIALOG (native)); -} - -void -_sysprof_display_reload_page (SysprofDisplay *self, - SysprofPage *page) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - SysprofSelection *selection; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - g_return_if_fail (SYSPROF_IS_PAGE (page)); - g_return_if_fail (priv->reader != NULL); - - selection = sysprof_visualizers_frame_get_selection (priv->visualizers); - - sysprof_page_load_async (page, - priv->reader, - selection, - priv->filter, - NULL, NULL, NULL); -} - -void -sysprof_display_add_to_selection (SysprofDisplay *self, - gint64 begin_time, - gint64 end_time) -{ - SysprofDisplayPrivate *priv = sysprof_display_get_instance_private (self); - SysprofSelection *selection; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - - selection = sysprof_visualizers_frame_get_selection (priv->visualizers); - sysprof_selection_select_range (selection, begin_time, end_time); -} - -void -_sysprof_display_destroy (SysprofDisplay *self) -{ - GtkWidget *parent; - - g_return_if_fail (SYSPROF_IS_DISPLAY (self)); - - if ((parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_NOTEBOOK))) - gtk_notebook_remove_page (GTK_NOTEBOOK (parent), - gtk_notebook_page_num (GTK_NOTEBOOK (parent), GTK_WIDGET (self))); -} diff --git a/src/libsysprof-ui/sysprof-display.h b/src/libsysprof-ui/sysprof-display.h deleted file mode 100644 index 16cd9f55..00000000 --- a/src/libsysprof-ui/sysprof-display.h +++ /dev/null @@ -1,96 +0,0 @@ -/* sysprof-display.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -#include "sysprof-page.h" -#include "sysprof-visualizer-group.h" -#include "sysprof-zoom-manager.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_DISPLAY (sysprof_display_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofDisplay, sysprof_display, SYSPROF, DISPLAY, GtkWidget) - -struct _SysprofDisplayClass -{ - GtkWidgetClass parent_class; - - /*< private >*/ - gpointer _reserved[16]; -}; - -SYSPROF_AVAILABLE_IN_ALL -GtkWidget *sysprof_display_new (void); -SYSPROF_AVAILABLE_IN_ALL -GtkWidget *sysprof_display_new_for_profiler (SysprofProfiler *profiler); -SYSPROF_AVAILABLE_IN_ALL -char *sysprof_display_dup_title (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -SysprofProfiler *sysprof_display_get_profiler (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_add_group (SysprofDisplay *self, - SysprofVisualizerGroup *group); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_add_page (SysprofDisplay *self, - SysprofPage *page); -SYSPROF_AVAILABLE_IN_ALL -SysprofPage *sysprof_display_get_visible_page (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_set_visible_page (SysprofDisplay *self, - SysprofPage *page); -SYSPROF_AVAILABLE_IN_ALL -SysprofZoomManager *sysprof_display_get_zoom_manager (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_load_async (SysprofDisplay *self, - SysprofCaptureReader *reader, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_display_load_finish (SysprofDisplay *self, - GAsyncResult *result, - GError **error); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_display_is_empty (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_open (SysprofDisplay *self, - GFile *file); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_save (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_display_get_can_save (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_display_stop_recording (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_display_get_can_replay (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_ALL -SysprofDisplay *sysprof_display_replay (SysprofDisplay *self); -SYSPROF_AVAILABLE_IN_3_38 -void sysprof_display_add_to_selection (SysprofDisplay *self, - gint64 begin_time, - gint64 end_time); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-display.ui b/src/libsysprof-ui/sysprof-display.ui deleted file mode 100644 index 61fceb7a..00000000 --- a/src/libsysprof-ui/sysprof-display.ui +++ /dev/null @@ -1,71 +0,0 @@ - - - - - diff --git a/src/libsysprof-ui/sysprof-duplex-visualizer.c b/src/libsysprof-ui/sysprof-duplex-visualizer.c deleted file mode 100644 index e9289a62..00000000 --- a/src/libsysprof-ui/sysprof-duplex-visualizer.c +++ /dev/null @@ -1,633 +0,0 @@ -/* sysprof-duplex-visualizer.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-duplex-visualizer" - -#include "config.h" - -#include "pointcache.h" -#include "sysprof-duplex-visualizer.h" - -#define LABEL_HEIGHT_PX 10 - -struct _SysprofDuplexVisualizer -{ - SysprofVisualizer parent_instance; - - gint64 begin_time; - gint64 duration; - - guint rx_counter; - guint tx_counter; - - GdkRGBA rx_rgba; - GdkRGBA tx_rgba; - - gchar *rx_label; - gchar *tx_label; - - PointCache *cache; - - guint rx_rgba_set : 1; - guint tx_rgba_set : 1; - guint use_diff : 1; -}; - -typedef struct -{ - PointCache *cache; - - gint64 begin_time; - gint64 duration; - - gint64 max_change; - - /* Last value to convert to rate of change */ - gint64 last_rx_val; - gint64 last_tx_val; - - /* Counter IDs */ - guint rx; - guint tx; - - /* Do we need to subtract previous value */ - guint use_diff : 1; -} Collect; - -G_DEFINE_TYPE (SysprofDuplexVisualizer, sysprof_duplex_visualizer, SYSPROF_TYPE_VISUALIZER) - -static bool -collect_ranges_cb (const SysprofCaptureFrame *frame, - gpointer data) -{ - Collect *state = data; - - g_assert (frame != NULL); - g_assert (state != NULL); - g_assert (state->cache != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET) - { - const SysprofCaptureCounterSet *set = (gconstpointer)frame; - - for (guint i = 0; i < set->n_values; i++) - { - const SysprofCaptureCounterValues *values = &set->values[i]; - - for (guint j = 0; j < G_N_ELEMENTS (values->ids); j++) - { - gint64 v64 = values->values[j].v64; - guint id = values->ids[j]; - gint64 max_change = 0; - - if (id == 0) - break; - - if (id == state->rx) - { - if (state->last_rx_val != G_MININT64) - max_change = v64 - state->last_rx_val; - state->last_rx_val = v64; - } - else if (id == state->tx) - { - if (state->last_tx_val != G_MININT64) - max_change = v64 - state->last_tx_val; - state->last_tx_val = v64; - } - else - { - continue; - } - - if (max_change > state->max_change) - state->max_change = max_change; - } - } - } - - return TRUE; -} - -static bool -collect_values_cb (const SysprofCaptureFrame *frame, - gpointer data) -{ - Collect *state = data; - - g_assert (frame != NULL); - g_assert (state != NULL); - g_assert (state->cache != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET) - { - const SysprofCaptureCounterSet *set = (gconstpointer)frame; - gdouble x = (frame->time - state->begin_time) / (gdouble)state->duration; - - for (guint i = 0; i < set->n_values; i++) - { - const SysprofCaptureCounterValues *values = &set->values[i]; - - for (guint j = 0; j < G_N_ELEMENTS (values->ids); j++) - { - gint64 v64 = values->values[j].v64; - guint id = values->ids[j]; - gint64 val = v64; - gdouble y = 0.5; - - if (id == 0) - break; - - if (id == state->rx) - { - if (state->use_diff) - { - if (state->last_rx_val == G_MININT64) - val = 0; - else - val -= state->last_rx_val; - } - - /* RX goes upward from half point */ - if (state->max_change != 0) - y += (gdouble)val / (gdouble)state->max_change / 2.0; - - state->last_rx_val = v64; - } - else if (id == state->tx) - { - if (state->use_diff) - { - if (state->last_tx_val == G_MININT64) - val = 0; - else - val -= state->last_tx_val; - } - - /* TX goes downward from half point */ - if (state->max_change != 0) - y -= (gdouble)val / (gdouble)state->max_change / 2.0; - - state->last_tx_val = v64; - } - else - { - continue; - } - - point_cache_add_point_to_set (state->cache, id, x, y); - } - } - } - - return TRUE; -} - -static void -sysprof_duplex_visualizer_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofCaptureCursor *cursor = task_data; - SysprofDuplexVisualizer *self = source_object; - Collect state = {0}; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - g_assert (cursor != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - state.cache = point_cache_new (); - state.begin_time = self->begin_time; - state.duration = self->duration; - state.rx = g_atomic_int_get (&self->rx_counter); - state.tx = g_atomic_int_get (&self->tx_counter); - state.last_rx_val = G_MININT64; - state.last_tx_val = G_MININT64; - state.max_change = 0; - state.use_diff = self->use_diff; - - point_cache_add_set (state.cache, state.rx); - point_cache_add_set (state.cache, state.tx); - - sysprof_capture_cursor_foreach (cursor, collect_ranges_cb, &state); - sysprof_capture_cursor_reset (cursor); - - /* Give just a bit of overhead */ - state.max_change *= 1.1; - - /* Reset for calculations */ - state.last_rx_val = G_MININT64; - state.last_tx_val = G_MININT64; - - sysprof_capture_cursor_foreach (cursor, collect_values_cb, &state); - - g_task_return_pointer (task, - g_steal_pointer (&state.cache), - (GDestroyNotify) point_cache_unref); -} - -static void -load_data_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofDuplexVisualizer *self = (SysprofDuplexVisualizer *)object; - g_autoptr(PointCache) pc = NULL; - - g_assert (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - g_assert (G_IS_TASK (result)); - - if ((pc = g_task_propagate_pointer (G_TASK (result), NULL))) - { - g_clear_pointer (&self->cache, point_cache_unref); - self->cache = g_steal_pointer (&pc); - gtk_widget_queue_draw (GTK_WIDGET (self)); - } -} - -static void -sysprof_duplex_visualizer_set_reader (SysprofVisualizer *visualizer, - SysprofCaptureReader *reader) -{ - SysprofDuplexVisualizer *self = (SysprofDuplexVisualizer *)visualizer; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - SysprofCaptureCondition *c; - guint counters[2]; - - g_assert (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - g_assert (reader != NULL); - - self->begin_time = sysprof_capture_reader_get_start_time (reader); - self->duration = sysprof_capture_reader_get_end_time (reader) - - sysprof_capture_reader_get_start_time (reader); - - counters[0] = self->rx_counter; - counters[1] = self->tx_counter; - - cursor = sysprof_capture_cursor_new (reader); - c = sysprof_capture_condition_new_where_counter_in (G_N_ELEMENTS (counters), counters); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&c)); - - task = g_task_new (self, NULL, load_data_cb, NULL); - g_task_set_source_tag (task, sysprof_duplex_visualizer_set_reader); - g_task_set_task_data (task, - g_steal_pointer (&cursor), - (GDestroyNotify)sysprof_capture_cursor_unref); - g_task_run_in_thread (task, sysprof_duplex_visualizer_worker); -} - -static void -sysprof_duplex_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - static const gdouble dashes[] = { 1.0, 2.0 }; - SysprofDuplexVisualizer *self = (SysprofDuplexVisualizer *)widget; - PangoFontDescription *font_desc; - GtkStyleContext *style_context; - PangoLayout *layout; - cairo_t *cr; - GtkAllocation alloc; - GdkRGBA fg; - guint mid; - - g_assert (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - g_assert (snapshot != NULL); - - /* FIXME: This should all be drawn offscreen and then drawn to the snapshot - * using GdkMemoryTexture so that we can avoid extra GPU uploads. - */ - - gtk_widget_get_allocation (widget, &alloc); - - mid = alloc.height / 2; - - GTK_WIDGET_CLASS (sysprof_duplex_visualizer_parent_class)->snapshot (widget, snapshot); - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height)); - - style_context = gtk_widget_get_style_context (widget); - gtk_style_context_get_color (style_context, &fg); - fg.alpha *= 0.4; - - /* Draw our center line */ - cairo_save (cr); - cairo_set_line_width (cr, 1.0); - cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0); - cairo_move_to (cr, 0, mid); - cairo_line_to (cr, alloc.width, mid); - gdk_cairo_set_source_rgba (cr, &fg); - cairo_stroke (cr); - cairo_restore (cr); - - if (self->cache != NULL) - { - g_autofree SysprofVisualizerAbsolutePoint *points = NULL; - const Point *fpoints; - guint n_fpoints = 0; - - cairo_save (cr); - cairo_set_line_width (cr, 1.0); - if (self->rx_rgba_set) - gdk_cairo_set_source_rgba (cr, &self->rx_rgba); - - fpoints = point_cache_get_points (self->cache, self->rx_counter, &n_fpoints); - - if (n_fpoints > 0) - { - GdkRGBA rgba = self->rx_rgba; - gdouble last_x; - gdouble last_y; - guint p; - - points = g_realloc_n (points, n_fpoints, sizeof *points); - - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (self), - (const SysprofVisualizerRelativePoint *)fpoints, - n_fpoints, - points, - n_fpoints); - - /* Skip past data that we won't see anyway */ -#if 0 - for (p = 0; p < n_fpoints; p++) - { - if (points[p].x >= clip.x) - break; - } - - if (p >= n_fpoints) - return; -#endif - - /* But get at least one data point to anchor out of view */ - if (p > 0) - p--; - - last_x = points[p].x; - last_y = points[p].y; - - cairo_move_to (cr, last_x, mid); - cairo_line_to (cr, last_x, last_y); - - for (guint i = p + 1; i < n_fpoints; i++) - { - cairo_curve_to (cr, - last_x + ((points[i].x - last_x) / 2), - last_y, - last_x + ((points[i].x - last_x) / 2), - points[i].y, - points[i].x, - points[i].y); - - last_x = points[i].x; - last_y = points[i].y; - -#if 0 - if (points[i].x > clip.x + clip.width) - break; -#endif - } - - cairo_line_to (cr, last_x, mid); - cairo_close_path (cr); - cairo_stroke_preserve (cr); - rgba.alpha *= 0.5; - gdk_cairo_set_source_rgba (cr, &rgba); - cairo_fill (cr); - } - - cairo_restore (cr); - - /* AND NOW Tx */ - - cairo_save (cr); - cairo_set_line_width (cr, 1.0); - if (self->tx_rgba_set) - gdk_cairo_set_source_rgba (cr, &self->tx_rgba); - - fpoints = point_cache_get_points (self->cache, self->tx_counter, &n_fpoints); - - if (n_fpoints > 0) - { - GdkRGBA rgba = self->tx_rgba; - gdouble last_x; - gdouble last_y; - guint p; - - points = g_realloc_n (points, n_fpoints, sizeof *points); - - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (self), - (const SysprofVisualizerRelativePoint *)fpoints, - n_fpoints, - points, - n_fpoints); - -#if 0 - /* Skip past data that we won't see anyway */ - for (p = 0; p < n_fpoints; p++) - { - if (points[p].x >= clip.x) - break; - } - - if (p >= n_fpoints) - return ret; -#endif - - /* But get at least one data point to anchor out of view */ - if (p > 0) - p--; - - last_x = points[p].x; - last_y = points[p].y; - - cairo_move_to (cr, last_x, mid); - cairo_line_to (cr, last_x, last_y); - - for (guint i = p + 1; i < n_fpoints; i++) - { - cairo_curve_to (cr, - last_x + ((points[i].x - last_x) / 2), - last_y, - last_x + ((points[i].x - last_x) / 2), - points[i].y, - points[i].x, - points[i].y); - - last_x = points[i].x; - last_y = points[i].y; - -#if 0 - if (points[i].x > clip.x + clip.width) - break; -#endif - } - - cairo_line_to (cr, last_x, mid); - cairo_close_path (cr); - cairo_stroke_preserve (cr); - rgba.alpha *= 0.5; - gdk_cairo_set_source_rgba (cr, &rgba); - cairo_fill (cr); - } - - cairo_restore (cr); - } - - layout = gtk_widget_create_pango_layout (widget, ""); - - font_desc = pango_font_description_new (); - pango_font_description_set_family_static (font_desc, "Monospace"); - pango_font_description_set_absolute_size (font_desc, LABEL_HEIGHT_PX * PANGO_SCALE); - pango_layout_set_font_description (layout, font_desc); - - gdk_cairo_set_source_rgba (cr, &fg); - - cairo_move_to (cr, 2, 2); - if (self->rx_label != NULL) - pango_layout_set_text (layout, self->rx_label, -1); - else - pango_layout_set_text (layout, "RX", 2); - pango_cairo_show_layout (cr, layout); - - cairo_move_to (cr, 2, mid + 2); - if (self->tx_label != NULL) - pango_layout_set_text (layout, self->tx_label, -1); - else - pango_layout_set_text (layout, "TX", 2); - pango_cairo_show_layout (cr, layout); - - pango_font_description_free (font_desc); - g_object_unref (layout); - - cairo_destroy (cr); -} - -static void -sysprof_duplex_visualizer_finalize (GObject *object) -{ - SysprofDuplexVisualizer *self = (SysprofDuplexVisualizer *)object; - - g_clear_pointer (&self->cache, point_cache_unref); - g_clear_pointer (&self->rx_label, g_free); - g_clear_pointer (&self->tx_label, g_free); - - G_OBJECT_CLASS (sysprof_duplex_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_duplex_visualizer_class_init (SysprofDuplexVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass); - - object_class->finalize = sysprof_duplex_visualizer_finalize; - - widget_class->snapshot = sysprof_duplex_visualizer_snapshot; - - visualizer_class->set_reader = sysprof_duplex_visualizer_set_reader; -} - -static void -sysprof_duplex_visualizer_init (SysprofDuplexVisualizer *self) -{ - self->use_diff = TRUE; -} - -GtkWidget * -sysprof_duplex_visualizer_new (void) -{ - return g_object_new (SYSPROF_TYPE_DUPLEX_VISUALIZER, NULL); -} - -void -sysprof_duplex_visualizer_set_counters (SysprofDuplexVisualizer *self, - guint rx_counter, - guint tx_counter) -{ - g_return_if_fail (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - g_return_if_fail (rx_counter != 0); - g_return_if_fail (tx_counter != 0); - - self->rx_counter = rx_counter; - self->tx_counter = tx_counter; -} - -void -sysprof_duplex_visualizer_set_colors (SysprofDuplexVisualizer *self, - const GdkRGBA *rx_rgba, - const GdkRGBA *tx_rgba) -{ - g_return_if_fail (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - - if (rx_rgba) - self->rx_rgba = *rx_rgba; - self->rx_rgba_set = !!rx_rgba; - - if (tx_rgba) - self->tx_rgba = *tx_rgba; - self->tx_rgba_set = !!tx_rgba; - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -gboolean -sysprof_duplex_visualizer_get_use_diff (SysprofDuplexVisualizer *self) -{ - g_return_val_if_fail (SYSPROF_IS_DUPLEX_VISUALIZER (self), FALSE); - - return self->use_diff; -} - -void -sysprof_duplex_visualizer_set_use_diff (SysprofDuplexVisualizer *self, - gboolean use_diff) -{ - g_return_if_fail (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - - self->use_diff = !!use_diff; - gtk_widget_queue_allocate (GTK_WIDGET (self)); -} - -void -sysprof_duplex_visualizer_set_labels (SysprofDuplexVisualizer *self, - const gchar *rx_label, - const gchar *tx_label) -{ - g_return_if_fail (SYSPROF_IS_DUPLEX_VISUALIZER (self)); - - if (g_strcmp0 (rx_label, self->rx_label) != 0) - { - g_free (self->rx_label); - self->rx_label = g_strdup (rx_label); - } - - if (g_strcmp0 (tx_label, self->tx_label) != 0) - { - g_free (self->tx_label); - self->tx_label = g_strdup (tx_label); - } - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} diff --git a/src/libsysprof-ui/sysprof-duplex-visualizer.h b/src/libsysprof-ui/sysprof-duplex-visualizer.h deleted file mode 100644 index 0b7dec53..00000000 --- a/src/libsysprof-ui/sysprof-duplex-visualizer.h +++ /dev/null @@ -1,45 +0,0 @@ -/* sysprof-duplex-visualizer.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-visualizer.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_DUPLEX_VISUALIZER (sysprof_duplex_visualizer_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofDuplexVisualizer, sysprof_duplex_visualizer, SYSPROF, DUPLEX_VISUALIZER, SysprofVisualizer) - -GtkWidget *sysprof_duplex_visualizer_new (void); -gboolean sysprof_duplex_visualizer_get_use_diff (SysprofDuplexVisualizer *self); -void sysprof_duplex_visualizer_set_use_diff (SysprofDuplexVisualizer *self, - gboolean use_diff); -void sysprof_duplex_visualizer_set_labels (SysprofDuplexVisualizer *self, - const gchar *rx_label, - const gchar *tx_label); -void sysprof_duplex_visualizer_set_counters (SysprofDuplexVisualizer *self, - guint rx_counter, - guint tx_counter); -void sysprof_duplex_visualizer_set_colors (SysprofDuplexVisualizer *self, - const GdkRGBA *rx_rgba, - const GdkRGBA *tx_rgba); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-environ-editor-row.c b/src/libsysprof-ui/sysprof-environ-editor-row.c deleted file mode 100644 index a7dd7b0c..00000000 --- a/src/libsysprof-ui/sysprof-environ-editor-row.c +++ /dev/null @@ -1,277 +0,0 @@ -/* sysprof-environ-editor-row.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-environ-editor-row" - -#include "config.h" - -#include "sysprof-environ-editor-row.h" - -struct _SysprofEnvironEditorRow -{ - GtkListBoxRow parent_instance; - - SysprofEnvironVariable *variable; - - GtkEntry *key_entry; - GtkEntry *value_entry; - GtkButton *delete_button; - - GBinding *key_binding; - GBinding *value_binding; -}; - -enum { - PROP_0, - PROP_VARIABLE, - LAST_PROP -}; - -enum { - DELETE, - LAST_SIGNAL -}; - -G_DEFINE_TYPE (SysprofEnvironEditorRow, sysprof_environ_editor_row, GTK_TYPE_LIST_BOX_ROW) - -static GParamSpec *properties [LAST_PROP]; -static guint signals [LAST_SIGNAL]; - -static gboolean -null_safe_mapping (GBinding *binding, - const GValue *from_value, - GValue *to_value, - gpointer user_data) -{ - const gchar *str = g_value_get_string (from_value); - g_value_set_string (to_value, str ?: ""); - return TRUE; -} - -static void -sysprof_environ_editor_row_connect (SysprofEnvironEditorRow *self) -{ - g_assert (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - g_assert (SYSPROF_IS_ENVIRON_VARIABLE (self->variable)); - - self->key_binding = - g_object_bind_property_full (self->variable, "key", self->key_entry, "text", - G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL, - null_safe_mapping, NULL, NULL, NULL); - - self->value_binding = - g_object_bind_property_full (self->variable, "value", self->value_entry, "text", - G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL, - null_safe_mapping, NULL, NULL, NULL); -} - -static void -sysprof_environ_editor_row_disconnect (SysprofEnvironEditorRow *self) -{ - g_assert (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - g_assert (SYSPROF_IS_ENVIRON_VARIABLE (self->variable)); - - g_clear_pointer (&self->key_binding, g_binding_unbind); - g_clear_pointer (&self->value_binding, g_binding_unbind); -} - -static void -delete_button_clicked (GtkButton *button, - SysprofEnvironEditorRow *self) -{ - g_assert (GTK_IS_BUTTON (button)); - g_assert (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - - g_signal_emit (self, signals [DELETE], 0); -} - -static void -key_entry_activate (GtkWidget *entry, - SysprofEnvironEditorRow *self) -{ - g_assert (GTK_IS_ENTRY (entry)); - g_assert (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - - gtk_widget_grab_focus (GTK_WIDGET (self->value_entry)); -} - -static void -value_entry_activate (GtkWidget *entry, - SysprofEnvironEditorRow *self) -{ - GtkWidget *parent; - - g_assert (GTK_IS_ENTRY (entry)); - g_assert (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - - gtk_widget_grab_focus (GTK_WIDGET (self)); - parent = gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_LIST_BOX); - g_signal_emit_by_name (parent, "move-cursor", GTK_MOVEMENT_DISPLAY_LINES, 1); -} - -static void -sysprof_environ_editor_row_dispose (GObject *object) -{ - SysprofEnvironEditorRow *self = (SysprofEnvironEditorRow *)object; - - if (self->variable != NULL) - { - sysprof_environ_editor_row_disconnect (self); - g_clear_object (&self->variable); - } - - G_OBJECT_CLASS (sysprof_environ_editor_row_parent_class)->dispose (object); -} - -static void -sysprof_environ_editor_row_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofEnvironEditorRow *self = SYSPROF_ENVIRON_EDITOR_ROW (object); - - switch (prop_id) - { - case PROP_VARIABLE: - g_value_set_object (value, sysprof_environ_editor_row_get_variable (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_environ_editor_row_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofEnvironEditorRow *self = SYSPROF_ENVIRON_EDITOR_ROW (object); - - switch (prop_id) - { - case PROP_VARIABLE: - sysprof_environ_editor_row_set_variable (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_environ_editor_row_class_init (SysprofEnvironEditorRowClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_environ_editor_row_dispose; - object_class->get_property = sysprof_environ_editor_row_get_property; - object_class->set_property = sysprof_environ_editor_row_set_property; - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-environ-editor-row.ui"); - gtk_widget_class_bind_template_child (widget_class, SysprofEnvironEditorRow, delete_button); - gtk_widget_class_bind_template_child (widget_class, SysprofEnvironEditorRow, key_entry); - gtk_widget_class_bind_template_child (widget_class, SysprofEnvironEditorRow, value_entry); - - properties [PROP_VARIABLE] = - g_param_spec_object ("variable", - "Variable", - "Variable", - SYSPROF_TYPE_ENVIRON_VARIABLE, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, LAST_PROP, properties); - - signals [DELETE] = - g_signal_new ("delete", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, G_TYPE_NONE, 0); -} - -static void -sysprof_environ_editor_row_init (SysprofEnvironEditorRow *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); - - g_signal_connect (self->delete_button, - "clicked", - G_CALLBACK (delete_button_clicked), - self); - - g_signal_connect (self->key_entry, - "activate", - G_CALLBACK (key_entry_activate), - self); - - g_signal_connect (self->value_entry, - "activate", - G_CALLBACK (value_entry_activate), - self); -} - -/** - * sysprof_environ_editor_row_get_variable: - * - * Returns: (transfer none) (nullable): An #SysprofEnvironVariable. - */ -SysprofEnvironVariable * -sysprof_environ_editor_row_get_variable (SysprofEnvironEditorRow *self) -{ - g_return_val_if_fail (SYSPROF_IS_ENVIRON_EDITOR_ROW (self), NULL); - - return self->variable; -} - -void -sysprof_environ_editor_row_set_variable (SysprofEnvironEditorRow *self, - SysprofEnvironVariable *variable) -{ - g_return_if_fail (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - g_return_if_fail (!variable || SYSPROF_IS_ENVIRON_VARIABLE (variable)); - - if (variable != self->variable) - { - if (self->variable != NULL) - { - sysprof_environ_editor_row_disconnect (self); - g_clear_object (&self->variable); - } - - if (variable != NULL) - { - self->variable = g_object_ref (variable); - sysprof_environ_editor_row_connect (self); - } - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VARIABLE]); - } -} - -void -sysprof_environ_editor_row_start_editing (SysprofEnvironEditorRow *self) -{ - g_return_if_fail (SYSPROF_IS_ENVIRON_EDITOR_ROW (self)); - - gtk_widget_grab_focus (GTK_WIDGET (self->key_entry)); -} diff --git a/src/libsysprof-ui/sysprof-environ-editor-row.h b/src/libsysprof-ui/sysprof-environ-editor-row.h deleted file mode 100644 index 39437e5a..00000000 --- a/src/libsysprof-ui/sysprof-environ-editor-row.h +++ /dev/null @@ -1,38 +0,0 @@ -/* ide-environ-editor-row.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-environ-variable.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_ENVIRON_EDITOR_ROW (sysprof_environ_editor_row_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofEnvironEditorRow, sysprof_environ_editor_row, SYSPROF, ENVIRON_EDITOR_ROW, GtkListBoxRow) - -SysprofEnvironVariable *sysprof_environ_editor_row_get_variable (SysprofEnvironEditorRow *self); -void sysprof_environ_editor_row_set_variable (SysprofEnvironEditorRow *self, - SysprofEnvironVariable *variable); -void sysprof_environ_editor_row_start_editing (SysprofEnvironEditorRow *self); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-environ-editor-row.ui b/src/libsysprof-ui/sysprof-environ-editor-row.ui deleted file mode 100644 index 8735f770..00000000 --- a/src/libsysprof-ui/sysprof-environ-editor-row.ui +++ /dev/null @@ -1,40 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-environ-editor.c b/src/libsysprof-ui/sysprof-environ-editor.c deleted file mode 100644 index 7c641aca..00000000 --- a/src/libsysprof-ui/sysprof-environ-editor.c +++ /dev/null @@ -1,343 +0,0 @@ -/* sysprof-environ-editor.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-environ-editor" - -#include "config.h" - -#include - -#include "sysprof-environ-editor.h" -#include "sysprof-environ-editor-row.h" -#include "sysprof-theme-manager.h" - -struct _SysprofEnvironEditor -{ - GtkWidget parent_instance; - GtkListBox *list_box; - SysprofEnviron *environ; - GtkWidget *dummy_row; - SysprofEnvironVariable *dummy; -}; - -G_DEFINE_TYPE (SysprofEnvironEditor, sysprof_environ_editor, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_ENVIRON, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_environ_editor_delete_row (SysprofEnvironEditor *self, - SysprofEnvironEditorRow *row) -{ - SysprofEnvironVariable *variable; - - g_assert (SYSPROF_IS_ENVIRON_EDITOR (self)); - g_assert (SYSPROF_IS_ENVIRON_EDITOR_ROW (row)); - - variable = sysprof_environ_editor_row_get_variable (row); - sysprof_environ_remove (self->environ, variable); -} - -static GtkWidget * -sysprof_environ_editor_create_dummy_row (SysprofEnvironEditor *self) -{ - GtkWidget *row; - GtkWidget *label; - - g_assert (SYSPROF_IS_ENVIRON_EDITOR (self)); - - label = g_object_new (GTK_TYPE_LABEL, - "label", _("New environment variable…"), - "margin-start", 6, - "margin-end", 6, - "margin-top", 6, - "margin-bottom", 6, - "visible", TRUE, - "xalign", 0.0f, - NULL); - gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); - - row = g_object_new (GTK_TYPE_LIST_BOX_ROW, - "child", label, - "visible", TRUE, - NULL); - - return row; -} - -static GtkWidget * -sysprof_environ_editor_create_row (gpointer item, - gpointer user_data) -{ - SysprofEnvironVariable *variable = item; - SysprofEnvironEditor *self = user_data; - SysprofEnvironEditorRow *row; - - g_assert (SYSPROF_IS_ENVIRON_EDITOR (self)); - g_assert (SYSPROF_IS_ENVIRON_VARIABLE (variable)); - - row = g_object_new (SYSPROF_TYPE_ENVIRON_EDITOR_ROW, - "variable", variable, - "visible", TRUE, - NULL); - - g_signal_connect_object (row, - "delete", - G_CALLBACK (sysprof_environ_editor_delete_row), - self, - G_CONNECT_SWAPPED); - - return GTK_WIDGET (row); -} - -static void -sysprof_environ_editor_disconnect (SysprofEnvironEditor *self) -{ - g_assert (SYSPROF_IS_ENVIRON_EDITOR (self)); - g_assert (SYSPROF_IS_ENVIRON (self->environ)); - - gtk_list_box_bind_model (self->list_box, NULL, NULL, NULL, NULL); - g_clear_object (&self->dummy); -} - -static void -sysprof_environ_editor_connect (SysprofEnvironEditor *self) -{ - g_assert (SYSPROF_IS_ENVIRON_EDITOR (self)); - g_assert (SYSPROF_IS_ENVIRON (self->environ)); - - gtk_list_box_bind_model (self->list_box, - G_LIST_MODEL (self->environ), - sysprof_environ_editor_create_row, self, NULL); - - self->dummy_row = sysprof_environ_editor_create_dummy_row (self); - gtk_list_box_append (self->list_box, self->dummy_row); -} - -static void -find_row_cb (GtkWidget *widget, - gpointer data) -{ - struct { - SysprofEnvironVariable *variable; - SysprofEnvironEditorRow *row; - } *lookup = data; - - g_assert (lookup != NULL); - g_assert (GTK_IS_LIST_BOX_ROW (widget)); - - if (SYSPROF_IS_ENVIRON_EDITOR_ROW (widget)) - { - SysprofEnvironVariable *variable; - - variable = sysprof_environ_editor_row_get_variable (SYSPROF_ENVIRON_EDITOR_ROW (widget)); - - if (variable == lookup->variable) - lookup->row = SYSPROF_ENVIRON_EDITOR_ROW (widget); - } -} - -static SysprofEnvironEditorRow * -find_row (SysprofEnvironEditor *self, - SysprofEnvironVariable *variable) -{ - struct { - SysprofEnvironVariable *variable; - SysprofEnvironEditorRow *row; - } lookup = { variable, NULL }; - - g_assert (SYSPROF_IS_ENVIRON_EDITOR (self)); - g_assert (SYSPROF_IS_ENVIRON_VARIABLE (variable)); - - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->list_box)); - child; - child = gtk_widget_get_next_sibling (child)) - find_row_cb (child, &lookup); - - return lookup.row; -} - -static void -sysprof_environ_editor_row_activated (SysprofEnvironEditor *self, - GtkListBoxRow *row, - GtkListBox *list_box) -{ - g_assert (GTK_IS_LIST_BOX (list_box)); - g_assert (GTK_IS_LIST_BOX_ROW (row)); - - if (self->environ == NULL) - return; - - if (self->dummy_row == GTK_WIDGET (row)) - { - g_autoptr(SysprofEnvironVariable) variable = NULL; - - variable = sysprof_environ_variable_new (NULL, NULL); - sysprof_environ_append (self->environ, variable); - sysprof_environ_editor_row_start_editing (find_row (self, variable)); - } -} - -static void -sysprof_environ_editor_dispose (GObject *object) -{ - SysprofEnvironEditor *self = (SysprofEnvironEditor *)object; - - if (self->list_box) - { - gtk_widget_unparent (GTK_WIDGET (self->list_box)); - self->list_box = NULL; - } - - g_clear_object (&self->environ); - - G_OBJECT_CLASS (sysprof_environ_editor_parent_class)->dispose (object); -} - -static void -sysprof_environ_editor_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofEnvironEditor *self = SYSPROF_ENVIRON_EDITOR (object); - - switch (prop_id) - { - case PROP_ENVIRON: - g_value_set_object (value, sysprof_environ_editor_get_environ (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - } -} - -static void -sysprof_environ_editor_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofEnvironEditor *self = SYSPROF_ENVIRON_EDITOR (object); - - switch (prop_id) - { - case PROP_ENVIRON: - sysprof_environ_editor_set_environ (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - } -} - -static void -sysprof_environ_editor_class_init (SysprofEnvironEditorClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofThemeManager *theme_manager = sysprof_theme_manager_get_default (); - - object_class->dispose = sysprof_environ_editor_dispose; - object_class->get_property = sysprof_environ_editor_get_property; - object_class->set_property = sysprof_environ_editor_set_property; - - properties [PROP_ENVIRON] = - g_param_spec_object ("environ", - "Environment", - "Environment", - SYSPROF_TYPE_ENVIRON, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - - sysprof_theme_manager_register_resource (theme_manager, NULL, NULL, "/org/gnome/sysprof/css/SysprofEnvironEditor-shared.css"); -} - -static void -sysprof_environ_editor_init (SysprofEnvironEditor *self) -{ - self->list_box = GTK_LIST_BOX (gtk_list_box_new ()); - gtk_widget_set_parent (GTK_WIDGET (self->list_box), GTK_WIDGET (self)); - - gtk_list_box_set_selection_mode (self->list_box, GTK_SELECTION_NONE); - - gtk_widget_add_css_class (GTK_WIDGET (self), "environ-editor"); - - g_signal_connect_object (self->list_box, - "row-activated", - G_CALLBACK (sysprof_environ_editor_row_activated), - self, - G_CONNECT_SWAPPED); -} - -GtkWidget * -sysprof_environ_editor_new (void) -{ - return g_object_new (SYSPROF_TYPE_ENVIRON_EDITOR, NULL); -} - -void -sysprof_environ_editor_set_environ (SysprofEnvironEditor *self, - SysprofEnviron *environ_) -{ - g_return_if_fail (SYSPROF_IS_ENVIRON_EDITOR (self)); - g_return_if_fail (SYSPROF_IS_ENVIRON (environ_)); - - if (self->environ != environ_) - { - if (self->environ != NULL) - { - sysprof_environ_editor_disconnect (self); - g_clear_object (&self->environ); - } - - if (environ_ != NULL) - { - self->environ = g_object_ref (environ_); - sysprof_environ_editor_connect (self); - } - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENVIRON]); - } -} - -/** - * sysprof_environ_editor_get_environ: - * - * Returns: (nullable) (transfer none): An #SysprofEnviron or %NULL. - * - * Since: 3.34 - */ -SysprofEnviron * -sysprof_environ_editor_get_environ (SysprofEnvironEditor *self) -{ - g_return_val_if_fail (SYSPROF_IS_ENVIRON_EDITOR (self), NULL); - - return self->environ; -} diff --git a/src/libsysprof-ui/sysprof-environ-variable.c b/src/libsysprof-ui/sysprof-environ-variable.c deleted file mode 100644 index 660b3457..00000000 --- a/src/libsysprof-ui/sysprof-environ-variable.c +++ /dev/null @@ -1,185 +0,0 @@ -/* sysprof-environ-variable.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-environ-variable" - -#include "config.h" - -#include "sysprof-environ-variable.h" - -struct _SysprofEnvironVariable -{ - GObject parent_instance; - gchar *key; - gchar *value; -}; - -G_DEFINE_TYPE (SysprofEnvironVariable, sysprof_environ_variable, G_TYPE_OBJECT) - -enum { - PROP_0, - PROP_KEY, - PROP_VALUE, - LAST_PROP -}; - -static GParamSpec *properties [LAST_PROP]; - -static void -sysprof_environ_variable_finalize (GObject *object) -{ - SysprofEnvironVariable *self = (SysprofEnvironVariable *)object; - - g_clear_pointer (&self->key, g_free); - g_clear_pointer (&self->value, g_free); - - G_OBJECT_CLASS (sysprof_environ_variable_parent_class)->finalize (object); -} - -static void -sysprof_environ_variable_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofEnvironVariable *self = SYSPROF_ENVIRON_VARIABLE(object); - - switch (prop_id) - { - case PROP_KEY: - g_value_set_string (value, self->key); - break; - - case PROP_VALUE: - g_value_set_string (value, self->value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - } -} - -static void -sysprof_environ_variable_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofEnvironVariable *self = SYSPROF_ENVIRON_VARIABLE(object); - - switch (prop_id) - { - case PROP_KEY: - sysprof_environ_variable_set_key (self, g_value_get_string (value)); - break; - - case PROP_VALUE: - sysprof_environ_variable_set_value (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - } -} - -static void -sysprof_environ_variable_class_init (SysprofEnvironVariableClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_environ_variable_finalize; - object_class->get_property = sysprof_environ_variable_get_property; - object_class->set_property = sysprof_environ_variable_set_property; - - properties [PROP_KEY] = - g_param_spec_string ("key", - "Key", - "The key for the environment variable", - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_VALUE] = - g_param_spec_string ("value", - "Value", - "The value for the environment variable", - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, LAST_PROP, properties); -} - -static void -sysprof_environ_variable_init (SysprofEnvironVariable *self) -{ -} - -const gchar * -sysprof_environ_variable_get_key (SysprofEnvironVariable *self) -{ - g_return_val_if_fail (SYSPROF_IS_ENVIRON_VARIABLE (self), NULL); - - return self->key; -} - -void -sysprof_environ_variable_set_key (SysprofEnvironVariable *self, - const gchar *key) -{ - g_return_if_fail (SYSPROF_IS_ENVIRON_VARIABLE (self)); - - if (g_strcmp0 (key, self->key) != 0) - { - g_free (self->key); - self->key = g_strdup (key); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KEY]); - } -} - -const gchar * -sysprof_environ_variable_get_value (SysprofEnvironVariable *self) -{ - g_return_val_if_fail (SYSPROF_IS_ENVIRON_VARIABLE (self), NULL); - - return self->value; -} - -void -sysprof_environ_variable_set_value (SysprofEnvironVariable *self, - const gchar *value) -{ - g_return_if_fail (SYSPROF_IS_ENVIRON_VARIABLE (self)); - - if (g_strcmp0 (value, self->value) != 0) - { - g_free (self->value); - self->value = g_strdup (value); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_VALUE]); - } -} - -SysprofEnvironVariable * -sysprof_environ_variable_new (const gchar *key, - const gchar *value) -{ - return g_object_new (SYSPROF_TYPE_ENVIRON_VARIABLE, - "key", key, - "value", value, - NULL); -} diff --git a/src/libsysprof-ui/sysprof-environ-variable.h b/src/libsysprof-ui/sysprof-environ-variable.h deleted file mode 100644 index ee19876a..00000000 --- a/src/libsysprof-ui/sysprof-environ-variable.h +++ /dev/null @@ -1,40 +0,0 @@ -/* sysprof-environ-variable.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_ENVIRON_VARIABLE (sysprof_environ_variable_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofEnvironVariable, sysprof_environ_variable, SYSPROF, ENVIRON_VARIABLE, GObject) - -SysprofEnvironVariable *sysprof_environ_variable_new (const gchar *key, - const gchar *value); -const gchar *sysprof_environ_variable_get_key (SysprofEnvironVariable *self); -void sysprof_environ_variable_set_key (SysprofEnvironVariable *self, - const gchar *key); -const gchar *sysprof_environ_variable_get_value (SysprofEnvironVariable *self); -void sysprof_environ_variable_set_value (SysprofEnvironVariable *self, - const gchar *value); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-environ.c b/src/libsysprof-ui/sysprof-environ.c deleted file mode 100644 index a1200d46..00000000 --- a/src/libsysprof-ui/sysprof-environ.c +++ /dev/null @@ -1,379 +0,0 @@ -/* sysprof-environ.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-environ" - -#include "config.h" - -#include - -#include "sysprof-environ.h" -#include "sysprof-environ-variable.h" - -struct _SysprofEnviron -{ - GObject parent_instance; - GPtrArray *variables; -}; - -static void list_model_iface_init (GListModelInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofEnviron, sysprof_environ, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) - -enum { - CHANGED, - LAST_SIGNAL -}; - -static guint signals [LAST_SIGNAL]; - -static void -sysprof_environ_finalize (GObject *object) -{ - SysprofEnviron *self = (SysprofEnviron *)object; - - g_clear_pointer (&self->variables, g_ptr_array_unref); - - G_OBJECT_CLASS (sysprof_environ_parent_class)->finalize (object); -} - -static void -sysprof_environ_class_init (SysprofEnvironClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_environ_finalize; - - signals [CHANGED] = - g_signal_new ("changed", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); - g_signal_set_va_marshaller (signals [CHANGED], - G_TYPE_FROM_CLASS (klass), - g_cclosure_marshal_VOID__VOIDv); -} - -static void -sysprof_environ_items_changed (SysprofEnviron *self) -{ - g_assert (SYSPROF_IS_ENVIRON (self)); - - g_signal_emit (self, signals [CHANGED], 0); -} - -static void -sysprof_environ_init (SysprofEnviron *self) -{ - self->variables = g_ptr_array_new_with_free_func (g_object_unref); - - g_signal_connect (self, - "items-changed", - G_CALLBACK (sysprof_environ_items_changed), - NULL); -} - -static GType -sysprof_environ_get_item_type (GListModel *model) -{ - return SYSPROF_TYPE_ENVIRON_VARIABLE; -} - -static gpointer -sysprof_environ_get_item (GListModel *model, - guint position) -{ - SysprofEnviron *self = (SysprofEnviron *)model; - - g_return_val_if_fail (SYSPROF_IS_ENVIRON (self), NULL); - g_return_val_if_fail (position < self->variables->len, NULL); - - return g_object_ref (g_ptr_array_index (self->variables, position)); -} - -static guint -sysprof_environ_get_n_items (GListModel *model) -{ - SysprofEnviron *self = (SysprofEnviron *)model; - - g_return_val_if_fail (SYSPROF_IS_ENVIRON (self), 0); - - return self->variables->len; -} - -static void -list_model_iface_init (GListModelInterface *iface) -{ - iface->get_n_items = sysprof_environ_get_n_items; - iface->get_item = sysprof_environ_get_item; - iface->get_item_type = sysprof_environ_get_item_type; -} - -static void -sysprof_environ_variable_notify (SysprofEnviron *self, - GParamSpec *pspec, - SysprofEnvironVariable *variable) -{ - g_assert (SYSPROF_IS_ENVIRON (self)); - - g_signal_emit (self, signals [CHANGED], 0); -} - -void -sysprof_environ_setenv (SysprofEnviron *self, - const gchar *key, - const gchar *value) -{ - guint i; - - g_return_if_fail (SYSPROF_IS_ENVIRON (self)); - g_return_if_fail (key != NULL); - - for (i = 0; i < self->variables->len; i++) - { - SysprofEnvironVariable *var = g_ptr_array_index (self->variables, i); - const gchar *var_key = sysprof_environ_variable_get_key (var); - - if (g_strcmp0 (key, var_key) == 0) - { - if (value == NULL) - { - g_ptr_array_remove_index (self->variables, i); - g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0); - return; - } - - sysprof_environ_variable_set_value (var, value); - return; - } - } - - if (value != NULL) - { - SysprofEnvironVariable *var; - guint position = self->variables->len; - - var = g_object_new (SYSPROF_TYPE_ENVIRON_VARIABLE, - "key", key, - "value", value, - NULL); - g_signal_connect_object (var, - "notify", - G_CALLBACK (sysprof_environ_variable_notify), - self, - G_CONNECT_SWAPPED); - g_ptr_array_add (self->variables, var); - g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); - } -} - -const gchar * -sysprof_environ_getenv (SysprofEnviron *self, - const gchar *key) -{ - guint i; - - g_return_val_if_fail (SYSPROF_IS_ENVIRON (self), NULL); - g_return_val_if_fail (key != NULL, NULL); - - for (i = 0; i < self->variables->len; i++) - { - SysprofEnvironVariable *var = g_ptr_array_index (self->variables, i); - const gchar *var_key = sysprof_environ_variable_get_key (var); - - if (g_strcmp0 (key, var_key) == 0) - return sysprof_environ_variable_get_value (var); - } - - return NULL; -} - -/** - * sysprof_environ_get_environ: - * @self: An #SysprofEnviron - * - * Gets the environment as a set of key=value pairs, suitable for use - * in various GLib process functions. - * - * Returns: (transfer full): A newly allocated string array. - * - * Since: 3.32 - */ -gchar ** -sysprof_environ_get_environ (SysprofEnviron *self) -{ - GPtrArray *ar; - guint i; - - g_return_val_if_fail (SYSPROF_IS_ENVIRON (self), NULL); - - ar = g_ptr_array_new (); - - for (i = 0; i < self->variables->len; i++) - { - SysprofEnvironVariable *var = g_ptr_array_index (self->variables, i); - const gchar *key = sysprof_environ_variable_get_key (var); - const gchar *value = sysprof_environ_variable_get_value (var); - - if (value == NULL) - value = ""; - - if (key != NULL) - g_ptr_array_add (ar, g_strdup_printf ("%s=%s", key, value)); - } - - g_ptr_array_add (ar, NULL); - - return (gchar **)g_ptr_array_free (ar, FALSE); -} - -SysprofEnviron * -sysprof_environ_new (void) -{ - return g_object_new (SYSPROF_TYPE_ENVIRON, NULL); -} - -void -sysprof_environ_remove (SysprofEnviron *self, - SysprofEnvironVariable *variable) -{ - guint i; - - g_return_if_fail (SYSPROF_IS_ENVIRON (self)); - g_return_if_fail (SYSPROF_IS_ENVIRON_VARIABLE (variable)); - - for (i = 0; i < self->variables->len; i++) - { - SysprofEnvironVariable *item = g_ptr_array_index (self->variables, i); - - if (item == variable) - { - g_ptr_array_remove_index (self->variables, i); - g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0); - break; - } - } -} - -void -sysprof_environ_append (SysprofEnviron *self, - SysprofEnvironVariable *variable) -{ - guint position; - - g_return_if_fail (SYSPROF_IS_ENVIRON (self)); - g_return_if_fail (SYSPROF_IS_ENVIRON_VARIABLE (variable)); - - position = self->variables->len; - - g_signal_connect_object (variable, - "notify", - G_CALLBACK (sysprof_environ_variable_notify), - self, - G_CONNECT_SWAPPED); - g_ptr_array_add (self->variables, g_object_ref (variable)); - g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); -} - -/** - * sysprof_environ_copy: - * @self: An #SysprofEnviron - * - * Copies the contents of #SysprofEnviron into a newly allocated #SysprofEnviron. - * - * Returns: (transfer full): An #SysprofEnviron. - * - * Since: 3.32 - */ -SysprofEnviron * -sysprof_environ_copy (SysprofEnviron *self) -{ - g_autoptr(SysprofEnviron) copy = NULL; - - g_return_val_if_fail (SYSPROF_IS_ENVIRON (self), NULL); - - copy = sysprof_environ_new (); - sysprof_environ_copy_into (self, copy, TRUE); - - return g_steal_pointer (©); -} - -void -sysprof_environ_copy_into (SysprofEnviron *self, - SysprofEnviron *dest, - gboolean replace) -{ - g_return_if_fail (SYSPROF_IS_ENVIRON (self)); - g_return_if_fail (SYSPROF_IS_ENVIRON (dest)); - - for (guint i = 0; i < self->variables->len; i++) - { - SysprofEnvironVariable *var = g_ptr_array_index (self->variables, i); - const gchar *key = sysprof_environ_variable_get_key (var); - const gchar *value = sysprof_environ_variable_get_value (var); - - if (replace || sysprof_environ_getenv (dest, key) == NULL) - sysprof_environ_setenv (dest, key, value); - } -} - -/** - * ide_environ_parse: - * @pair: the KEY=VALUE pair - * @key: (out) (optional): a location for a @key - * @value: (out) (optional): a location for a @value - * - * Parses a KEY=VALUE style key-pair into @key and @value. - * - * Returns: %TRUE if @pair was successfully parsed - * - * Since: 3.32 - */ -gboolean -ide_environ_parse (const gchar *pair, - gchar **key, - gchar **value) -{ - const gchar *eq; - - g_return_val_if_fail (pair != NULL, FALSE); - - if (key != NULL) - *key = NULL; - - if (value != NULL) - *value = NULL; - - if ((eq = strchr (pair, '='))) - { - if (key != NULL) - *key = g_strndup (pair, eq - pair); - - if (value != NULL) - *value = g_strdup (eq + 1); - - return TRUE; - } - - return FALSE; -} diff --git a/src/libsysprof-ui/sysprof-environ.h b/src/libsysprof-ui/sysprof-environ.h deleted file mode 100644 index ef5e5f83..00000000 --- a/src/libsysprof-ui/sysprof-environ.h +++ /dev/null @@ -1,52 +0,0 @@ -/* sysprof-environ.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-environ-variable.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_ENVIRON (sysprof_environ_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofEnviron, sysprof_environ, SYSPROF, ENVIRON, GObject) - -gboolean ide_environ_parse (const gchar *pair, - gchar **key, - gchar **value); -SysprofEnviron *sysprof_environ_new (void); -void sysprof_environ_setenv (SysprofEnviron *self, - const gchar *key, - const gchar *value); -const gchar *sysprof_environ_getenv (SysprofEnviron *self, - const gchar *key); -gchar **sysprof_environ_get_environ (SysprofEnviron *self); -void sysprof_environ_append (SysprofEnviron *self, - SysprofEnvironVariable *variable); -void sysprof_environ_remove (SysprofEnviron *self, - SysprofEnvironVariable *variable); -SysprofEnviron *sysprof_environ_copy (SysprofEnviron *self); -void sysprof_environ_copy_into (SysprofEnviron *self, - SysprofEnviron *dest, - gboolean replace); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-failed-state-view.c b/src/libsysprof-ui/sysprof-failed-state-view.c deleted file mode 100644 index 6ca02e7d..00000000 --- a/src/libsysprof-ui/sysprof-failed-state-view.c +++ /dev/null @@ -1,62 +0,0 @@ -/* sysprof-failed-state-view.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-failed-state-view.h" - -G_DEFINE_TYPE (SysprofFailedStateView, sysprof_failed_state_view, GTK_TYPE_WIDGET) - -GtkWidget * -sysprof_failed_state_view_new (void) -{ - return g_object_new (SYSPROF_TYPE_FAILED_STATE_VIEW, NULL); -} - -static void -sysprof_failed_state_view_dispose (GObject *object) -{ - SysprofFailedStateView *self = (SysprofFailedStateView *)object; - GtkWidget *child; - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - gtk_widget_unparent (child); - - G_OBJECT_CLASS (sysprof_failed_state_view_parent_class)->dispose (object); -} - -static void -sysprof_failed_state_view_class_init (SysprofFailedStateViewClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_failed_state_view_dispose; - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_set_template_from_resource (widget_class, - "/org/gnome/sysprof/ui/sysprof-failed-state-view.ui"); -} - -static void -sysprof_failed_state_view_init (SysprofFailedStateView *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); -} diff --git a/src/libsysprof-ui/sysprof-failed-state-view.ui b/src/libsysprof-ui/sysprof-failed-state-view.ui deleted file mode 100644 index 8b01a009..00000000 --- a/src/libsysprof-ui/sysprof-failed-state-view.ui +++ /dev/null @@ -1,47 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-line-visualizer.c b/src/libsysprof-ui/sysprof-line-visualizer.c deleted file mode 100644 index 046be4a8..00000000 --- a/src/libsysprof-ui/sysprof-line-visualizer.c +++ /dev/null @@ -1,909 +0,0 @@ -/* sysprof-line-visualizer.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-line-visualizer" - -#include "config.h" - -#include -#include -#include - -#include "pointcache.h" -#include "sysprof-line-visualizer.h" - -typedef struct -{ - /* - * Our reader as assigned by the visualizer system. - */ - SysprofCaptureReader *reader; - - /* - * An array of LineInfo which contains information about the counters - * we need to render. - */ - GArray *lines; - - /* - * This is our set of cached points to render. Once it is assigned here, - * it is immutable (and therefore may be shared with worker processes - * that are rendering the points). - */ - PointCache *cache; - - /* The format for units (such as mHz, Watts, etc). */ - gchar *units; - - /* - * Range of the scale for lower and upper. - */ - gdouble y_lower; - gdouble y_upper; - - /* - * If we have a new counter discovered or the reader is set, we might - * want to delay loading until we return to the main loop. This can - * help us avoid doing duplicate work. - */ - guint queued_load; - - guint y_lower_set : 1; - guint y_upper_set : 1; -} SysprofLineVisualizerPrivate; - -typedef struct -{ - guint id; - guint type; - gdouble line_width; - GdkRGBA foreground; - GdkRGBA background; - guint use_default_style : 1; - guint fill : 1; - guint use_dash : 1; -} LineInfo; - -typedef struct -{ - SysprofCaptureCursor *cursor; - GArray *lines; - PointCache *cache; - gint64 begin_time; - gint64 end_time; - gdouble y_lower; - gdouble y_upper; - guint y_lower_set : 1; - guint y_upper_set : 1; -} LoadData; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofLineVisualizer, sysprof_line_visualizer, SYSPROF_TYPE_VISUALIZER) - -static void sysprof_line_visualizer_load_data_async (SysprofLineVisualizer *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -static PointCache *sysprof_line_visualizer_load_data_finish (SysprofLineVisualizer *self, - GAsyncResult *result, - GError **error); - -enum { - PROP_0, - PROP_Y_LOWER, - PROP_Y_UPPER, - PROP_UNITS, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; -static const gdouble dashes[] = { 1.0, 2.0 }; - -static void -load_data_free (gpointer data) -{ - LoadData *load = data; - - if (load != NULL) - { - g_clear_pointer (&load->lines, g_array_unref); - g_clear_pointer (&load->cursor, sysprof_capture_cursor_unref); - g_clear_pointer (&load->cache, point_cache_unref); - g_slice_free (LoadData, load); - } -} - -static GArray * -copy_array (GArray *ar) -{ - GArray *ret; - - ret = g_array_sized_new (FALSE, FALSE, g_array_get_element_size (ar), ar->len); - g_array_set_size (ret, ar->len); - memcpy (ret->data, ar->data, ar->len * g_array_get_element_size (ret)); - - return ret; -} - -static void -sysprof_line_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - static PangoAttrList *attrs = NULL; - SysprofLineVisualizer *self = (SysprofLineVisualizer *)widget; - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - g_autofree gchar *upper = NULL; - GtkStyleContext *style_context; - PangoLayout *layout; - cairo_t *cr; - GtkAllocation alloc; - GdkRectangle clip; - GdkRGBA foreground; - - g_assert (SYSPROF_IS_LINE_VISUALIZER (widget)); - g_assert (snapshot != NULL); - - gtk_widget_get_allocation (widget, &alloc); - - GTK_WIDGET_CLASS (sysprof_line_visualizer_parent_class)->snapshot (widget, snapshot); - - if (priv->cache == NULL) - return; - -#if 0 - if (!gdk_cairo_get_clip_rectangle (cr, &clip)) - return ret; -#else - clip.x = 0; - clip.y = 0; - clip.width = alloc.width; - clip.height = alloc.height; -#endif - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height)); - - style_context = gtk_widget_get_style_context (widget); - gtk_style_context_get_color (style_context, &foreground); - - for (guint line = 0; line < priv->lines->len; line++) - { - g_autofree SysprofVisualizerAbsolutePoint *points = NULL; - const LineInfo *line_info = &g_array_index (priv->lines, LineInfo, line); - const Point *fpoints; - guint n_fpoints = 0; - GdkRGBA color; - - fpoints = point_cache_get_points (priv->cache, line_info->id, &n_fpoints); - - if (n_fpoints > 0) - { - gdouble last_x = 0; - gdouble last_y = 0; - guint p; - - points = g_new0 (SysprofVisualizerAbsolutePoint, n_fpoints); - - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (self), - (const SysprofVisualizerRelativePoint *)fpoints, - n_fpoints, - points, - n_fpoints); - - for (p = 0; p < n_fpoints; p++) - { - if (points[p].x >= clip.x) - break; - } - - if (p >= n_fpoints) - goto cleanup; - - if (p > 0) - p--; - - last_x = points[p].x; - last_y = points[p].y; - - if (line_info->fill) - { - cairo_move_to (cr, last_x, alloc.height); - cairo_line_to (cr, last_x, last_y); - } - else - { - cairo_move_to (cr, last_x, last_y); - } - - for (guint i = p + 1; i < n_fpoints; i++) - { - cairo_curve_to (cr, - last_x + ((points[i].x - last_x) / 2), - last_y, - last_x + ((points[i].x - last_x) / 2), - points[i].y, - points[i].x, - points[i].y); - - last_x = points[i].x; - last_y = points[i].y; - - if (points[i].x > clip.x + clip.width) - break; - } - - if (line_info->fill) - { - cairo_line_to (cr, last_x, alloc.height); - cairo_close_path (cr); - } - - cairo_set_line_width (cr, line_info->line_width); - - if (line_info->use_dash) - cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0); - - if (line_info->fill) - { - gdk_cairo_set_source_rgba (cr, &line_info->background); - cairo_fill_preserve (cr); - } - - if (line_info->use_default_style) - color = foreground; - else - color = line_info->foreground; - - gdk_cairo_set_source_rgba (cr, &color); - cairo_stroke (cr); - } - } - - if (!attrs) - { - attrs = pango_attr_list_new (); - pango_attr_list_insert (attrs, pango_attr_scale_new (0.666)); - } - - if (priv->y_upper != 100.0) - { - if (priv->units) - upper = g_strdup_printf ("%lg %s", priv->y_upper, priv->units); - else - upper = g_strdup_printf ("%lg", priv->y_upper); - - layout = gtk_widget_create_pango_layout (widget, upper); - pango_layout_set_attributes (layout, attrs); - cairo_move_to (cr, 2, 2); - foreground.alpha *= 0.5; - gdk_cairo_set_source_rgba (cr, &foreground); - pango_cairo_show_layout (cr, layout); - g_clear_object (&layout); - } - -cleanup: - cairo_destroy (cr); -} - -static void -sysprof_line_visualizer_load_data_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofLineVisualizer *self = (SysprofLineVisualizer *)object; - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - g_autoptr(GError) error = NULL; - g_autoptr(PointCache) cache = NULL; - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - - cache = sysprof_line_visualizer_load_data_finish (self, result, &error); - - if (cache == NULL) - { - g_warning ("%s", error->message); - return; - } - - g_clear_pointer (&priv->cache, point_cache_unref); - priv->cache = g_steal_pointer (&cache); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static gboolean -sysprof_line_visualizer_do_reload (gpointer data) -{ - SysprofLineVisualizer *self = data; - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - - priv->queued_load = 0; - - if (priv->reader != NULL) - { - sysprof_line_visualizer_load_data_async (self, - NULL, - sysprof_line_visualizer_load_data_cb, - NULL); - } - - return G_SOURCE_REMOVE; -} - -static void -sysprof_line_visualizer_queue_reload (SysprofLineVisualizer *self) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - - if (priv->queued_load == 0) - priv->queued_load = g_idle_add_full (G_PRIORITY_LOW, - sysprof_line_visualizer_do_reload, - self, - NULL); -} - -static void -sysprof_line_visualizer_set_reader (SysprofVisualizer *row, - SysprofCaptureReader *reader) -{ - SysprofLineVisualizer *self = (SysprofLineVisualizer *)row; - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - - if (priv->reader != reader) - { - if (priv->reader != NULL) - { - sysprof_capture_reader_unref (priv->reader); - priv->reader = NULL; - } - - if (reader != NULL) - priv->reader = sysprof_capture_reader_ref (reader); - - sysprof_line_visualizer_queue_reload (self); - } -} - -static void -sysprof_line_visualizer_finalize (GObject *object) -{ - SysprofLineVisualizer *self = (SysprofLineVisualizer *)object; - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_clear_pointer (&priv->units, g_free); - g_clear_pointer (&priv->lines, g_array_unref); - g_clear_pointer (&priv->cache, point_cache_unref); - g_clear_pointer (&priv->reader, sysprof_capture_reader_unref); - - g_clear_handle_id (&priv->queued_load, g_source_remove); - - G_OBJECT_CLASS (sysprof_line_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_line_visualizer_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofLineVisualizer *self = SYSPROF_LINE_VISUALIZER (object); - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - switch (prop_id) - { - case PROP_Y_LOWER: - g_value_set_double (value, priv->y_lower); - break; - - case PROP_Y_UPPER: - g_value_set_double (value, priv->y_upper); - break; - - case PROP_UNITS: - g_value_set_string (value, priv->units); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_line_visualizer_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofLineVisualizer *self = SYSPROF_LINE_VISUALIZER (object); - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - switch (prop_id) - { - case PROP_Y_LOWER: - priv->y_lower = g_value_get_double (value); - priv->y_lower_set = TRUE; - gtk_widget_queue_allocate (GTK_WIDGET (self)); - break; - - case PROP_Y_UPPER: - priv->y_upper = g_value_get_double (value); - priv->y_upper_set = TRUE; - gtk_widget_queue_allocate (GTK_WIDGET (self)); - break; - - case PROP_UNITS: - g_free (priv->units); - priv->units = g_value_dup_string (value); - gtk_widget_queue_allocate (GTK_WIDGET (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_line_visualizer_class_init (SysprofLineVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass); - - object_class->finalize = sysprof_line_visualizer_finalize; - object_class->get_property = sysprof_line_visualizer_get_property; - object_class->set_property = sysprof_line_visualizer_set_property; - - widget_class->snapshot = sysprof_line_visualizer_snapshot; - - visualizer_class->set_reader = sysprof_line_visualizer_set_reader; - - properties [PROP_Y_LOWER] = - g_param_spec_double ("y-lower", - "Y Lower", - "The lowest Y value for the visualizer", - -G_MAXDOUBLE, - G_MAXDOUBLE, - 0.0, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_Y_UPPER] = - g_param_spec_double ("y-upper", - "Y Upper", - "The highest Y value for the visualizer", - -G_MAXDOUBLE, - G_MAXDOUBLE, - 100.0, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_UNITS] = - g_param_spec_string ("units", - "Units", - "The format for units (mHz, Watts, etc)", - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_line_visualizer_init (SysprofLineVisualizer *self) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - priv->lines = g_array_new (FALSE, FALSE, sizeof (LineInfo)); -} - -void -sysprof_line_visualizer_add_counter (SysprofLineVisualizer *self, - guint counter_id, - const GdkRGBA *color) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - LineInfo line_info = { 0 }; - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - g_assert (priv->lines != NULL); - - line_info.id = counter_id; - line_info.line_width = 1.0; - line_info.type = SYSPROF_CAPTURE_COUNTER_DOUBLE; - - if (color != NULL) - { - line_info.foreground = *color; - line_info.use_default_style = FALSE; - } - else - { - gdk_rgba_parse (&line_info.foreground, "#000"); - line_info.use_default_style = TRUE; - } - - g_array_append_val (priv->lines, line_info); - - if (SYSPROF_LINE_VISUALIZER_GET_CLASS (self)->counter_added) - SYSPROF_LINE_VISUALIZER_GET_CLASS (self)->counter_added (self, counter_id); - - sysprof_line_visualizer_queue_reload (self); -} - -void -sysprof_line_visualizer_clear (SysprofLineVisualizer *self) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LINE_VISUALIZER (self)); - - if (priv->lines->len > 0) - g_array_remove_range (priv->lines, 0, priv->lines->len); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static inline gboolean -contains_id (GArray *ar, - guint id) -{ - for (guint i = 0; i < ar->len; i++) - { - const LineInfo *info = &g_array_index (ar, LineInfo, i); - - if (info->id == id) - return TRUE; - } - - return FALSE; -} - -static inline guint8 -counter_type (LoadData *load, - guint counter_id) -{ - for (guint i = 0; i < load->lines->len; i++) - { - const LineInfo *info = &g_array_index (load->lines, LineInfo, i); - - if (info->id == counter_id) - return info->type; - } - - return SYSPROF_CAPTURE_COUNTER_DOUBLE; -} - -static inline gdouble -calc_x (gint64 lower, - gint64 upper, - gint64 value) -{ - return (gdouble)(value - lower) / (gdouble)(upper - lower); -} - -static inline gdouble -calc_y_double (gdouble lower, - gdouble upper, - gdouble value) -{ - return (value - lower) / (upper - lower); -} - -static inline gdouble -calc_y_int64 (gint64 lower, - gint64 upper, - gint64 value) -{ - return (gdouble)(value - lower) / (gdouble)(upper - lower); -} - -static bool -sysprof_line_visualizer_load_data_frame_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - LoadData *load = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET || - frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (load != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET) - { - const SysprofCaptureCounterSet *set = (SysprofCaptureCounterSet *)frame; - gdouble x = calc_x (load->begin_time, load->end_time, frame->time); - - for (guint i = 0; i < set->n_values; i++) - { - const SysprofCaptureCounterValues *group = &set->values[i]; - - for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++) - { - guint counter_id = group->ids[j]; - - if (counter_id != 0 && contains_id (load->lines, counter_id)) - { - gdouble y; - - if (counter_type (load, counter_id) == SYSPROF_CAPTURE_COUNTER_DOUBLE) - y = calc_y_double (load->y_lower, load->y_upper, group->values[j].vdbl); - else - y = calc_y_int64 (load->y_lower, load->y_upper, group->values[j].v64); - - point_cache_add_point_to_set (load->cache, counter_id, x, y); - } - } - } - } - - return TRUE; -} - -static bool -sysprof_line_visualizer_load_data_range_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - LoadData *load = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET || - frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (load != NULL); - g_assert (load->y_upper_set == FALSE || - load->y_lower_set == FALSE); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF) - { - const SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - - for (guint i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *ctr = &def->counters[i]; - - for (guint j = 0; j < load->lines->len; j++) - { - LineInfo *info = &g_array_index (load->lines, LineInfo, j); - - if (info->id == ctr->id) - { - info->type = ctr->type; - break; - } - } - } - } - else if (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET) - { - const SysprofCaptureCounterSet *set = (SysprofCaptureCounterSet *)frame; - - for (guint i = 0; i < set->n_values; i++) - { - const SysprofCaptureCounterValues *group = &set->values[i]; - - for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++) - { - guint counter_id = group->ids[j]; - - if (counter_id != 0 && contains_id (load->lines, counter_id)) - { - gdouble y; - - if (counter_type (load, counter_id) == SYSPROF_CAPTURE_COUNTER_DOUBLE) - y = group->values[j].vdbl; - else - y = group->values[j].v64; - - if (!load->y_upper_set) - load->y_upper = MAX (load->y_upper, y); - - if (!load->y_lower_set) - load->y_lower = MIN (load->y_lower, y); - } - } - } - } - - return TRUE; -} - -static void -sysprof_line_visualizer_load_data_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - LoadData *load = task_data; - g_autoptr(GArray) counter_ids = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_LINE_VISUALIZER (source_object)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - counter_ids = g_array_new (FALSE, FALSE, sizeof (guint)); - - for (guint i = 0; i < load->lines->len; i++) - { - const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i); - g_array_append_val (counter_ids, line_info->id); - } - - sysprof_capture_cursor_add_condition (load->cursor, - sysprof_capture_condition_new_where_counter_in (counter_ids->len, - (guint *)(gpointer)counter_ids->data)); - - /* If y boundaries are not set, we need to discover them by scaning the data. */ - if (!load->y_lower_set || !load->y_upper_set) - { - sysprof_capture_cursor_foreach (load->cursor, sysprof_line_visualizer_load_data_range_cb, load); - sysprof_capture_cursor_reset (load->cursor); - - /* Add extra boundary for some space above the graph line */ - if (G_MAXDOUBLE - load->y_upper > (load->y_upper * .25)) - load->y_upper = load->y_upper + ((load->y_upper - load->y_lower) * .25); - } - - sysprof_capture_cursor_foreach (load->cursor, sysprof_line_visualizer_load_data_frame_cb, load); - g_task_return_pointer (task, g_steal_pointer (&load->cache), (GDestroyNotify)point_cache_unref); -} - -static void -sysprof_line_visualizer_load_data_async (SysprofLineVisualizer *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - g_autoptr(GTask) task = NULL; - LoadData *load; - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_priority (task, G_PRIORITY_LOW); - g_task_set_source_tag (task, sysprof_line_visualizer_load_data_async); - - if (priv->reader == NULL) - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "No data loaded"); - return; - } - - load = g_slice_new0 (LoadData); - load->cache = point_cache_new (); - load->y_lower = priv->y_lower_set ? priv->y_lower : G_MAXDOUBLE; - load->y_upper = priv->y_upper_set ? priv->y_upper : -G_MAXDOUBLE; - load->y_lower_set = priv->y_lower_set; - load->y_upper_set = priv->y_upper_set; - load->begin_time = sysprof_capture_reader_get_start_time (priv->reader); - load->end_time = sysprof_capture_reader_get_end_time (priv->reader); - load->cursor = sysprof_capture_cursor_new (priv->reader); - load->lines = copy_array (priv->lines); - - for (guint i = 0; i < load->lines->len; i++) - { - const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i); - - point_cache_add_set (load->cache, line_info->id); - } - - g_task_set_task_data (task, load, load_data_free); - g_task_run_in_thread (task, sysprof_line_visualizer_load_data_worker); -} - -static PointCache * -sysprof_line_visualizer_load_data_finish (SysprofLineVisualizer *self, - GAsyncResult *result, - GError **error) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - LoadData *state; - - g_assert (SYSPROF_IS_LINE_VISUALIZER (self)); - g_assert (G_IS_TASK (result)); - - state = g_task_get_task_data (G_TASK (result)); - - if (!priv->y_lower_set && priv->y_lower != state->y_lower) - { - priv->y_lower = state->y_lower; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_Y_LOWER]); - } - - if (!priv->y_upper_set && priv->y_upper != state->y_upper) - { - priv->y_upper = state->y_upper; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_Y_UPPER]); - } - - return g_task_propagate_pointer (G_TASK (result), error); -} - -void -sysprof_line_visualizer_set_line_width (SysprofLineVisualizer *self, - guint counter_id, - gdouble width) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LINE_VISUALIZER (self)); - - for (guint i = 0; i < priv->lines->len; i++) - { - LineInfo *info = &g_array_index (priv->lines, LineInfo, i); - - if (info->id == counter_id) - { - info->line_width = width; - sysprof_line_visualizer_queue_reload (self); - break; - } - } -} - -void -sysprof_line_visualizer_set_fill (SysprofLineVisualizer *self, - guint counter_id, - const GdkRGBA *color) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LINE_VISUALIZER (self)); - - for (guint i = 0; i < priv->lines->len; i++) - { - LineInfo *info = &g_array_index (priv->lines, LineInfo, i); - - if (info->id == counter_id) - { - info->fill = !!color; - if (color != NULL) - info->background = *color; - sysprof_line_visualizer_queue_reload (self); - break; - } - } -} - -void -sysprof_line_visualizer_set_dash (SysprofLineVisualizer *self, - guint counter_id, - gboolean use_dash) -{ - SysprofLineVisualizerPrivate *priv = sysprof_line_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LINE_VISUALIZER (self)); - - for (guint i = 0; i < priv->lines->len; i++) - { - LineInfo *info = &g_array_index (priv->lines, LineInfo, i); - - if (info->id == counter_id) - { - info->use_dash = !!use_dash; - sysprof_line_visualizer_queue_reload (self); - break; - } - } -} diff --git a/src/libsysprof-ui/sysprof-line-visualizer.h b/src/libsysprof-ui/sysprof-line-visualizer.h deleted file mode 100644 index 4fac8855..00000000 --- a/src/libsysprof-ui/sysprof-line-visualizer.h +++ /dev/null @@ -1,57 +0,0 @@ -/* sysprof-line-visualizer.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-visualizer.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_LINE_VISUALIZER (sysprof_line_visualizer_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofLineVisualizer, sysprof_line_visualizer, SYSPROF, LINE_VISUALIZER, SysprofVisualizer) - -struct _SysprofLineVisualizerClass -{ - SysprofVisualizerClass parent_class; - - void (*counter_added) (SysprofLineVisualizer *self, - guint counter_id); - - /*< private >*/ - gpointer _reserved[16]; -}; - -GtkWidget *sysprof_line_visualizer_new (void); -void sysprof_line_visualizer_clear (SysprofLineVisualizer *self); -void sysprof_line_visualizer_add_counter (SysprofLineVisualizer *self, - guint counter_id, - const GdkRGBA *color); -void sysprof_line_visualizer_set_line_width (SysprofLineVisualizer *self, - guint counter_id, - gdouble width); -void sysprof_line_visualizer_set_fill (SysprofLineVisualizer *self, - guint counter_id, - const GdkRGBA *color); -void sysprof_line_visualizer_set_dash (SysprofLineVisualizer *self, - guint counter_id, - gboolean use_dash); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-log-model.c b/src/libsysprof-ui/sysprof-log-model.c deleted file mode 100644 index 8d67254f..00000000 --- a/src/libsysprof-ui/sysprof-log-model.c +++ /dev/null @@ -1,422 +0,0 @@ -/* sysprof-log-model.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-log-model" - -#include "config.h" - -#include -#include -#include - -#include "sysprof-log-model.h" - -struct _SysprofLogModel -{ - GObject parent_instance; - GStringChunk *chunks; - GArray *items; - gint64 begin_time; -}; - -typedef struct -{ - gint64 time; - const gchar *domain; - const gchar *message; - guint16 severity; -} Item; - -static gint -sysprof_log_model_get_n_columns (GtkTreeModel *model) -{ - return SYSPROF_LOG_MODEL_COLUMN_LAST; -} - -static GType -sysprof_log_model_get_column_type (GtkTreeModel *model, - gint column) -{ - switch (column) - { - case SYSPROF_LOG_MODEL_COLUMN_TIME: - return G_TYPE_INT64; - - case SYSPROF_LOG_MODEL_COLUMN_SEVERITY: - case SYSPROF_LOG_MODEL_COLUMN_DOMAIN: - case SYSPROF_LOG_MODEL_COLUMN_MESSAGE: - case SYSPROF_LOG_MODEL_COLUMN_TIME_STRING: - return G_TYPE_STRING; - - default: - return 0; - } -} - -static GtkTreePath * -sysprof_log_model_get_path (GtkTreeModel *model, - GtkTreeIter *iter) -{ - gint off; - - g_assert (SYSPROF_IS_LOG_MODEL (model)); - g_assert (iter != NULL); - - off = GPOINTER_TO_INT (iter->user_data); - - return gtk_tree_path_new_from_indices (off, -1); -} - -static gboolean -sysprof_log_model_get_iter (GtkTreeModel *model, - GtkTreeIter *iter, - GtkTreePath *path) -{ - SysprofLogModel *self = (SysprofLogModel *)model; - gint off; - - g_assert (SYSPROF_IS_LOG_MODEL (self)); - g_assert (iter != NULL); - g_assert (path != NULL); - - memset (iter, 0, sizeof *iter); - - if (gtk_tree_path_get_depth (path) != 1) - return FALSE; - - off = gtk_tree_path_get_indices (path)[0]; - iter->user_data = GINT_TO_POINTER (off); - - return off >= 0 && off < self->items->len; -} - -static gboolean -sysprof_log_model_iter_next (GtkTreeModel *model, - GtkTreeIter *iter) -{ - SysprofLogModel *self = (SysprofLogModel *)model; - gint off; - - g_assert (SYSPROF_IS_LOG_MODEL (self)); - g_assert (iter != NULL); - - off = GPOINTER_TO_INT (iter->user_data); - off++; - iter->user_data = GINT_TO_POINTER (off); - - return off < self->items->len; -} - -static gboolean -sysprof_log_model_iter_nth_child (GtkTreeModel *model, - GtkTreeIter *iter, - GtkTreeIter *parent, - gint n) -{ - SysprofLogModel *self = (SysprofLogModel *)model; - - g_assert (SYSPROF_IS_LOG_MODEL (self)); - g_assert (iter != NULL); - - if (parent != NULL) - return FALSE; - - iter->user_data = GINT_TO_POINTER (n); - - return n < self->items->len; -} - -static gint -sysprof_log_model_iter_n_children (GtkTreeModel *model, - GtkTreeIter *iter) -{ - SysprofLogModel *self = (SysprofLogModel *)model; - - g_assert (SYSPROF_IS_LOG_MODEL (self)); - - return iter ? 0 : self->items->len; -} - -static gboolean -sysprof_log_model_iter_has_child (GtkTreeModel *model, - GtkTreeIter *iter) -{ - return FALSE; -} - -static GtkTreeModelFlags -sysprof_log_model_get_flags (GtkTreeModel *model) -{ - return GTK_TREE_MODEL_LIST_ONLY; -} - -static void -sysprof_log_model_get_value (GtkTreeModel *model, - GtkTreeIter *iter, - gint column, - GValue *value) -{ - SysprofLogModel *self = (SysprofLogModel *)model; - const Item *item; - - g_assert (SYSPROF_IS_LOG_MODEL (self)); - g_assert (iter != NULL); - g_assert (column < SYSPROF_LOG_MODEL_COLUMN_LAST); - - item = &g_array_index (self->items, Item, GPOINTER_TO_INT (iter->user_data)); - - switch (column) - { - case SYSPROF_LOG_MODEL_COLUMN_TIME_STRING: - { - gint64 offset = item->time - self->begin_time; - gint min = offset / SYSPROF_NSEC_PER_SEC / 60L; - gint seconds = ((offset - (min * SYSPROF_NSEC_PER_SEC)) / SYSPROF_NSEC_PER_SEC) % 60; - gint msec = (offset % SYSPROF_NSEC_PER_SEC) / (SYSPROF_NSEC_PER_SEC / 1000L); - - g_value_init (value, G_TYPE_STRING); - g_value_take_string (value, - g_strdup_printf ("%02d:%02d.%03d", min, seconds, msec)); - } - break; - - case SYSPROF_LOG_MODEL_COLUMN_TIME: - g_value_init (value, G_TYPE_INT64); - g_value_set_int64 (value, item->time); - break; - - case SYSPROF_LOG_MODEL_COLUMN_SEVERITY: - g_value_init (value, G_TYPE_STRING); - switch (item->severity) - { - case G_LOG_LEVEL_MESSAGE: - g_value_set_static_string (value, _("Message")); - break; - case G_LOG_LEVEL_INFO: - g_value_set_static_string (value, _("Info")); - break; - case G_LOG_LEVEL_CRITICAL: - g_value_set_static_string (value, _("Critical")); - break; - case G_LOG_LEVEL_ERROR: - g_value_set_static_string (value, _("Error")); - break; - case G_LOG_LEVEL_DEBUG: - g_value_set_static_string (value, _("Debug")); - break; - case G_LOG_LEVEL_WARNING: - g_value_set_static_string (value, _("Warning")); - break; - default: - g_value_set_static_string (value, ""); - break; - } - break; - - case SYSPROF_LOG_MODEL_COLUMN_DOMAIN: - g_value_init (value, G_TYPE_STRING); - g_value_set_string (value, item->domain); - break; - - case SYSPROF_LOG_MODEL_COLUMN_MESSAGE: - g_value_init (value, G_TYPE_STRING); - g_value_set_string (value, item->message); - break; - - default: - break; - } -} - -static void -tree_model_iface_init (GtkTreeModelIface *iface) -{ - iface->get_n_columns = sysprof_log_model_get_n_columns; - iface->get_column_type = sysprof_log_model_get_column_type; - iface->get_iter = sysprof_log_model_get_iter; - iface->get_path = sysprof_log_model_get_path; - iface->iter_next = sysprof_log_model_iter_next; - iface->iter_n_children = sysprof_log_model_iter_n_children; - iface->iter_nth_child = sysprof_log_model_iter_nth_child; - iface->iter_has_child = sysprof_log_model_iter_has_child; - iface->get_flags = sysprof_log_model_get_flags; - iface->get_value = sysprof_log_model_get_value; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofLogModel, sysprof_log_model, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, tree_model_iface_init)) - -static void -sysprof_log_model_finalize (GObject *object) -{ - SysprofLogModel *self = (SysprofLogModel *)object; - - g_clear_pointer (&self->items, g_array_unref); - g_clear_pointer (&self->chunks, g_string_chunk_free); - - G_OBJECT_CLASS (sysprof_log_model_parent_class)->finalize (object); -} - -static void -sysprof_log_model_class_init (SysprofLogModelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_log_model_finalize; -} - -static void -sysprof_log_model_init (SysprofLogModel *self) -{ - self->chunks = g_string_chunk_new (4096*16); - self->items = g_array_new (FALSE, FALSE, sizeof (Item)); -} - -static bool -cursor_foreach_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofLogModel *self = user_data; - SysprofCaptureLog *log = (SysprofCaptureLog *)frame; - Item item; - - g_assert (SYSPROF_IS_LOG_MODEL (self)); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_LOG); - - item.time = frame->time; - item.severity = log->severity; - item.domain = g_string_chunk_insert_const (self->chunks, log->domain); - item.message = g_string_chunk_insert_const (self->chunks, log->message); - - g_array_append_val (self->items, item); - - return TRUE; -} - -static gint -item_compare (gconstpointer a, - gconstpointer b) -{ - const Item *ia = a; - const Item *ib = b; - - if (ia->time < ib->time) - return -1; - else if (ia->time > ib->time) - return 1; - else - return 0; -} - -static void -sysprof_log_model_new_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - g_autoptr(SysprofLogModel) self = NULL; - SysprofCaptureCursor *cursor = task_data; - SysprofCaptureReader *reader; - - g_assert (G_IS_TASK (task)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - self = g_object_new (SYSPROF_TYPE_LOG_MODEL, NULL); - - reader = sysprof_capture_cursor_get_reader (cursor); - self->begin_time = sysprof_capture_reader_get_start_time (reader); - - sysprof_capture_cursor_foreach (cursor, cursor_foreach_cb, self); - g_array_sort (self->items, item_compare); - - g_task_return_pointer (task, g_steal_pointer (&self), g_object_unref); -} - -static void -sysprof_log_model_selection_foreach_cb (SysprofSelection *selection, - gint64 begin, - gint64 end, - gpointer user_data) -{ - SysprofCaptureCondition **condition = user_data; - SysprofCaptureCondition *c; - - g_assert (SYSPROF_IS_SELECTION (selection)); - g_assert (condition != NULL); - - c = sysprof_capture_condition_new_where_time_between (begin, end); - - if (*condition != NULL) - *condition = sysprof_capture_condition_new_or (g_steal_pointer (&c), - g_steal_pointer (condition)); - else - *condition = g_steal_pointer (&c); -} - -void -sysprof_log_model_new_async (SysprofCaptureReader *reader, - SysprofSelection *selection, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_LOG, - }; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - SysprofCaptureCondition *c; - g_autoptr(GTask) task = NULL; - - g_return_if_fail (reader != NULL); - g_return_if_fail (!selection || SYSPROF_IS_SELECTION (selection)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - cursor = sysprof_capture_cursor_new (reader); - c = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - - if (selection) - { - SysprofCaptureCondition *condition = NULL; - - sysprof_selection_foreach (selection, - sysprof_log_model_selection_foreach_cb, - &condition); - if (condition) - c = sysprof_capture_condition_new_and (c, g_steal_pointer (&condition)); - } - - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&c)); - - task = g_task_new (NULL, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_log_model_new_async); - g_task_set_task_data (task, - g_steal_pointer (&cursor), - (GDestroyNotify) sysprof_capture_cursor_unref); - g_task_run_in_thread (task, sysprof_log_model_new_worker); -} - -SysprofLogModel * -sysprof_log_model_new_finish (GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (G_IS_TASK (result), NULL); - - return g_task_propagate_pointer (G_TASK (result), error); -} diff --git a/src/libsysprof-ui/sysprof-log-model.h b/src/libsysprof-ui/sysprof-log-model.h deleted file mode 100644 index 7edb25fa..00000000 --- a/src/libsysprof-ui/sysprof-log-model.h +++ /dev/null @@ -1,49 +0,0 @@ -/* sysprof-log-model.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef enum -{ - SYSPROF_LOG_MODEL_COLUMN_TIME, - SYSPROF_LOG_MODEL_COLUMN_SEVERITY, - SYSPROF_LOG_MODEL_COLUMN_DOMAIN, - SYSPROF_LOG_MODEL_COLUMN_MESSAGE, - SYSPROF_LOG_MODEL_COLUMN_TIME_STRING, - SYSPROF_LOG_MODEL_COLUMN_LAST -} SysprofLogModelColumn; - -#define SYSPROF_TYPE_LOG_MODEL (sysprof_log_model_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofLogModel, sysprof_log_model, SYSPROF, LOG_MODEL, GObject) - -void sysprof_log_model_new_async (SysprofCaptureReader *reader, - SysprofSelection *selection, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -SysprofLogModel *sysprof_log_model_new_finish (GAsyncResult *result, - GError **error); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-logs-aid.c b/src/libsysprof-ui/sysprof-logs-aid.c deleted file mode 100644 index 8807cbe5..00000000 --- a/src/libsysprof-ui/sysprof-logs-aid.c +++ /dev/null @@ -1,237 +0,0 @@ -/* sysprof-logs-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-logs-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-logs-aid.h" -#include "sysprof-logs-page.h" -#include "sysprof-mark-visualizer.h" - -struct _SysprofLogsAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofDisplay *display; - SysprofCaptureCursor *cursor; - GArray *log_marks; -} Present; - -G_DEFINE_TYPE (SysprofLogsAid, sysprof_logs_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->log_marks, g_array_unref); - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -static void -on_group_activated_cb (SysprofVisualizerGroup *group, - SysprofPage *page) -{ - SysprofDisplay *display; - - g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); - g_assert (SYSPROF_IS_PAGE (page)); - - display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY)); - sysprof_display_set_visible_page (display, page); -} - -/** - * sysprof_logs_aid_new: - * - * Create a new #SysprofLogsAid. - * - * Returns: (transfer full): a newly created #SysprofLogsAid - */ -SysprofAid * -sysprof_logs_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_LOGS_AID, NULL); -} - -static bool -find_marks_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - Present *p = user_data; - - g_assert (frame != NULL); - g_assert (p != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_LOG) - { - SysprofMarkTimeSpan span = { frame->time, frame->time }; - g_array_append_val (p->log_marks, span); - } - - return TRUE; -} - -static gint -compare_span (const SysprofMarkTimeSpan *a, - const SysprofMarkTimeSpan *b) -{ - if (a->kind < b->kind) - return -1; - - if (b->kind < a->kind) - return 1; - - if (a->begin < b->begin) - return -1; - - if (b->begin < a->begin) - return 1; - - if (b->end > a->end) - return -1; - - return 0; -} - -static void -sysprof_logs_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *p = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (p != NULL); - g_assert (SYSPROF_IS_DISPLAY (p->display)); - g_assert (p->cursor != NULL); - g_assert (SYSPROF_IS_LOGS_AID (source_object)); - - sysprof_capture_cursor_foreach (p->cursor, find_marks_cb, p); - g_array_sort (p->log_marks, (GCompareFunc)compare_span); - - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_logs_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType logs[] = { - SYSPROF_CAPTURE_FRAME_LOG, - }; - SysprofLogsAid *self = (SysprofLogsAid *)aid; - g_autoptr(GTask) task = NULL; - Present p = {0}; - - g_assert (SYSPROF_IS_LOGS_AID (self)); - - p.display = g_object_ref (display); - p.log_marks = g_array_new (FALSE, FALSE, sizeof (SysprofMarkTimeSpan)); - p.cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (p.cursor, - sysprof_capture_condition_new_where_type_in (1, logs)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_logs_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &p), - present_free); - g_task_run_in_thread (task, sysprof_logs_aid_present_worker); -} - -static gboolean -sysprof_logs_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - Present *p; - - g_assert (SYSPROF_IS_LOGS_AID (aid)); - g_assert (G_IS_TASK (result)); - - p = g_task_get_task_data (G_TASK (result)); - - if (p->log_marks->len > 0) - { - g_autoptr(GHashTable) items = NULL; - SysprofVisualizerGroup *group; - SysprofVisualizer *marks; - SysprofPage *page; - - items = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) g_array_unref); - g_hash_table_insert (items, g_strdup (_("Logs")), g_array_ref (p->log_marks)); - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "title", _("Logs"), - "visible", TRUE, - NULL); - - marks = sysprof_mark_visualizer_new (items); - sysprof_visualizer_set_title (marks, _("Logs")); - gtk_widget_show (GTK_WIDGET (marks)); - sysprof_visualizer_group_insert (group, marks, 0, FALSE); - sysprof_display_add_group (p->display, group); - - page = g_object_new (SYSPROF_TYPE_LOGS_PAGE, - "title", _("Logs"), - "visible", TRUE, - NULL); - sysprof_display_add_page (p->display, page); - - g_signal_connect_object (group, - "group-activated", - G_CALLBACK (on_group_activated_cb), - page, - 0); - } - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_logs_aid_class_init (SysprofLogsAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->present_async = sysprof_logs_aid_present_async; - aid_class->present_finish = sysprof_logs_aid_present_finish; -} - -static void -sysprof_logs_aid_init (SysprofLogsAid *self) -{ -} diff --git a/src/libsysprof-ui/sysprof-logs-page.c b/src/libsysprof-ui/sysprof-logs-page.c deleted file mode 100644 index f2b46af8..00000000 --- a/src/libsysprof-ui/sysprof-logs-page.c +++ /dev/null @@ -1,116 +0,0 @@ -/* sysprof-logs-page.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-logs-page" - -#include "config.h" - -#include "sysprof-log-model.h" -#include "sysprof-logs-page.h" - -struct _SysprofLogsPage -{ - SysprofPage parent_instance; - - /* Template Widgets */ - GtkTreeView *tree_view; -}; - -G_DEFINE_TYPE (SysprofLogsPage, sysprof_logs_page, SYSPROF_TYPE_PAGE) - -static void -sysprof_logs_page_load_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofLogsPage *self; - g_autoptr(SysprofLogModel) model = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!(model = sysprof_log_model_new_finish (result, &error))) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_boolean (task, TRUE); - - self = g_task_get_source_object (task); - - gtk_tree_view_set_model (self->tree_view, GTK_TREE_MODEL (model)); -} - -static void -sysprof_logs_page_load_async (SysprofPage *page, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *filter, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofLogsPage *self = (SysprofLogsPage *)page; - g_autoptr(GTask) task = NULL; - - g_assert (SYSPROF_IS_LOGS_PAGE (self)); - g_assert (reader != NULL); - g_assert (!selection || SYSPROF_IS_SELECTION (selection)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_logs_page_load_async); - - sysprof_log_model_new_async (reader, - selection, - cancellable, - sysprof_logs_page_load_cb, - g_steal_pointer (&task)); -} - -static gboolean -sysprof_logs_page_load_finish (SysprofPage *page, - GAsyncResult *result, - GError **error) -{ - g_assert (SYSPROF_IS_LOGS_PAGE (page)); - g_assert (G_IS_TASK (result)); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_logs_page_class_init (SysprofLogsPageClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofPageClass *page_class = SYSPROF_PAGE_CLASS (klass); - - page_class->load_async = sysprof_logs_page_load_async; - page_class->load_finish = sysprof_logs_page_load_finish; - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-logs-page.ui"); - gtk_widget_class_bind_template_child (widget_class, SysprofLogsPage, tree_view); -} - -static void -sysprof_logs_page_init (SysprofLogsPage *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); -} diff --git a/src/libsysprof-ui/sysprof-logs-page.ui b/src/libsysprof-ui/sysprof-logs-page.ui deleted file mode 100644 index 2f4d565b..00000000 --- a/src/libsysprof-ui/sysprof-logs-page.ui +++ /dev/null @@ -1,73 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-mark-detail.c b/src/libsysprof-ui/sysprof-mark-detail.c deleted file mode 100644 index d0899149..00000000 --- a/src/libsysprof-ui/sysprof-mark-detail.c +++ /dev/null @@ -1,208 +0,0 @@ -/* sysprof-mark-detail.c - * - * Copyright 2022 Corentin Noël - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-mark-detail" - -#include "config.h" - -#include - -#include "sysprof-mark-detail.h" - -struct _SysprofMarkDetail -{ - GObject parent_instance; - - gchar *label; - gint64 min; - gint64 max; - gint64 average; - gint64 hits; -}; - -G_DEFINE_TYPE (SysprofMarkDetail, sysprof_mark_detail, G_TYPE_OBJECT) - -enum { - PROP_0, - PROP_LABEL, - PROP_MIN, - PROP_MAX, - PROP_AVERAGE, - PROP_HITS, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -/** - * sysprof_mark_detail_new: - * - * Create a new #SysprofMarkDetail. - * - * Returns: (transfer full): a newly created #SysprofMarkDetail - */ -SysprofMarkDetail * -sysprof_mark_detail_new (const gchar *mark, - gint64 min, - gint64 max, - gint64 avg, - gint64 hits) -{ - return g_object_new (SYSPROF_TYPE_MARK_DETAIL, - "label", mark, - "min", min, - "max", max, - "average", avg, - "hits", hits, - NULL); -} - -static void -sysprof_mark_detail_finalize (GObject *object) -{ - SysprofMarkDetail *self = (SysprofMarkDetail *)object; - - g_clear_pointer (&self->label, g_free); - - G_OBJECT_CLASS (sysprof_mark_detail_parent_class)->finalize (object); -} - -static void -sysprof_mark_detail_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofMarkDetail *self = SYSPROF_MARK_DETAIL(object); - - switch (prop_id) - { - case PROP_LABEL: - g_value_set_string (value, self->label); - break; - - case PROP_MIN: - g_value_set_int64 (value, self->min); - break; - - case PROP_MAX: - g_value_set_int64 (value, self->max); - break; - - case PROP_AVERAGE: - g_value_set_int64 (value, self->average); - break; - - case PROP_HITS: - g_value_set_int64 (value, self->hits); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_mark_detail_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofMarkDetail *self = SYSPROF_MARK_DETAIL(object); - - switch (prop_id) - { - case PROP_LABEL: - g_assert(self->label == NULL); - self->label = g_value_dup_string (value); - break; - - case PROP_MIN: - self->min = g_value_get_int64 (value); - break; - - case PROP_MAX: - self->max = g_value_get_int64 (value); - break; - - case PROP_AVERAGE: - self->average = g_value_get_int64 (value); - break; - - case PROP_HITS: - self->hits = g_value_get_int64 (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_mark_detail_class_init (SysprofMarkDetailClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_mark_detail_finalize; - object_class->get_property = sysprof_mark_detail_get_property; - object_class->set_property = sysprof_mark_detail_set_property; - - properties [PROP_LABEL] = - g_param_spec_string ("label", - "Label", - "The label of the mark", - NULL, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_MIN] = - g_param_spec_int64 ("min", - "Min", - "The minimal timespan", - 0, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_MAX] = - g_param_spec_int64 ("max", - "max", - "The maximal timespan", - 0, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_AVERAGE] = - g_param_spec_int64 ("average", - "Average", - "The average timespan", - 0, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_HITS] = - g_param_spec_int64 ("hits", - "Hits", - "The number of hits", - 0, G_MAXINT64, 0, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_mark_detail_init (SysprofMarkDetail *self) -{ -} diff --git a/src/libsysprof-ui/sysprof-mark-detail.h b/src/libsysprof-ui/sysprof-mark-detail.h deleted file mode 100644 index 35b292e7..00000000 --- a/src/libsysprof-ui/sysprof-mark-detail.h +++ /dev/null @@ -1,37 +0,0 @@ -/* sysprof-mark-detail.h - * - * Copyright 2022 Corentin Noël - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MARK_DETAIL (sysprof_mark_detail_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofMarkDetail, sysprof_mark_detail, SYSPROF, MARK_DETAIL, GObject) - -SysprofMarkDetail *sysprof_mark_detail_new (const gchar *mark, - gint64 min, - gint64 max, - gint64 avg, - gint64 hits); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-mark-visualizer.c b/src/libsysprof-ui/sysprof-mark-visualizer.c deleted file mode 100644 index ab8a67f9..00000000 --- a/src/libsysprof-ui/sysprof-mark-visualizer.c +++ /dev/null @@ -1,276 +0,0 @@ -/* sysprof-mark-visualizer.c - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-mark-visualizer" - -#include "config.h" - -#include "sysprof-mark-visualizer.h" - -#define RECT_HEIGHT (4) -#define RECT_MIN_WIDTH (3) -#define RECT_OVERLAP (-1) - -struct _SysprofMarkVisualizer -{ - SysprofVisualizer parent_instance; - GHashTable *spans_by_group; - GHashTable *rgba_by_group; - GHashTable *rgba_by_kind; - GHashTable *row_by_kind; - guint x_is_dirty : 1; -}; - -G_DEFINE_TYPE (SysprofMarkVisualizer, sysprof_mark_visualizer, SYSPROF_TYPE_VISUALIZER) - -static void -reset_positions (SysprofMarkVisualizer *self) -{ - g_assert (SYSPROF_IS_MARK_VISUALIZER (self)); - - self->x_is_dirty = TRUE; - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -SysprofVisualizer * -sysprof_mark_visualizer_new (GHashTable *groups) -{ - SysprofMarkVisualizer *self; - guint n_items; - gint height; - - g_return_val_if_fail (groups != NULL, NULL); - - self = g_object_new (SYSPROF_TYPE_MARK_VISUALIZER, NULL); - self->spans_by_group = g_hash_table_ref (groups); - - reset_positions (self); - - n_items = g_hash_table_size (groups); - height = MAX (35, n_items * (RECT_HEIGHT - RECT_OVERLAP)); - gtk_widget_set_size_request (GTK_WIDGET (self), -1, height); - - return SYSPROF_VISUALIZER (g_steal_pointer (&self)); -} - -static void -sysprof_mark_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofMarkVisualizer *self = (SysprofMarkVisualizer *)widget; - SysprofVisualizer *vis = (SysprofVisualizer *)widget; - static const GdkRGBA black = {0,0,0,1}; - const GdkRGBA *rgba = &black; - GHashTableIter iter; - GtkAllocation alloc; - gpointer k, v; - int n_groups = 0; - int y = 0; - - g_assert (SYSPROF_IS_MARK_VISUALIZER (self)); - g_assert (snapshot != NULL); - - GTK_WIDGET_CLASS (sysprof_mark_visualizer_parent_class)->snapshot (widget, snapshot); - - if (self->spans_by_group == NULL) - return; - - gtk_widget_get_allocation (widget, &alloc); - - /* Pre-calculate all time slots so we can join later */ - if (self->x_is_dirty) - { - g_hash_table_iter_init (&iter, self->spans_by_group); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - const GArray *spans = v; - - for (guint i = 0; i < spans->len; i++) - { - SysprofMarkTimeSpan *span = &g_array_index (spans, SysprofMarkTimeSpan, i); - - span->x = sysprof_visualizer_get_x_for_time (vis, span->begin); - span->x2 = sysprof_visualizer_get_x_for_time (vis, span->end); - } - } - - self->x_is_dirty = FALSE; - } - - n_groups = g_hash_table_size (self->spans_by_group); - - g_hash_table_iter_init (&iter, self->spans_by_group); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - SysprofMarkTimeSpan *span; - const gchar *group = k; - const GArray *spans = v; - const GdkRGBA *kindrgba; - const GdkRGBA *grouprgba; - - if ((grouprgba = g_hash_table_lookup (self->rgba_by_group, group))) - rgba = grouprgba; - - for (guint i = 0; i < spans->len; i++) - { - gint x1, x2; - - span = &g_array_index (spans, SysprofMarkTimeSpan, i); - - if (n_groups == 1) - { - rgba = &black; - if ((kindrgba = g_hash_table_lookup (self->rgba_by_kind, GUINT_TO_POINTER (span->kind)))) - rgba = kindrgba; - else if ((grouprgba = g_hash_table_lookup (self->rgba_by_group, group))) - rgba = grouprgba; - } - - x1 = span->x; - x2 = x1 + RECT_MIN_WIDTH; - - if (span->x2 > x2) - x2 = span->x2; - - /* If we are limited to one group, we might need to get the row - * height for the kind of span this is. - */ - if (n_groups == 1) - { - gint row = GPOINTER_TO_INT (g_hash_table_lookup (self->row_by_kind, GUINT_TO_POINTER (span->kind))); - y = row * (RECT_HEIGHT - RECT_OVERLAP); - } - - for (guint j = i + 1; j < spans->len; j++) - { - const SysprofMarkTimeSpan *next = &g_array_index (spans, SysprofMarkTimeSpan, j); - - /* Don't join this if we are about to draw a different kind */ - if (n_groups == 1 && next->kind != span->kind) - break; - - if (next->x <= x2) - { - x2 = MAX (x2, next->x2); - i++; - continue; - } - - break; - } - - gtk_snapshot_append_color (snapshot, rgba, &GRAPHENE_RECT_INIT (x1, y, x2 - x1, RECT_HEIGHT)); - } - - y += RECT_HEIGHT + RECT_OVERLAP; - } -} - -static void -sysprof_mark_visualizer_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - SysprofMarkVisualizer *self = (SysprofMarkVisualizer *)widget; - - g_assert (SYSPROF_IS_MARK_VISUALIZER (self)); - - GTK_WIDGET_CLASS (sysprof_mark_visualizer_parent_class)->size_allocate (widget, width, height, baseline); - - reset_positions (self); -} - -static void -sysprof_mark_visualizer_finalize (GObject *object) -{ - SysprofMarkVisualizer *self = (SysprofMarkVisualizer *)object; - - g_clear_pointer (&self->spans_by_group, g_hash_table_unref); - g_clear_pointer (&self->rgba_by_group, g_hash_table_unref); - g_clear_pointer (&self->rgba_by_kind, g_hash_table_unref); - g_clear_pointer (&self->row_by_kind, g_hash_table_unref); - - G_OBJECT_CLASS (sysprof_mark_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_mark_visualizer_class_init (SysprofMarkVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->finalize = sysprof_mark_visualizer_finalize; - - widget_class->snapshot = sysprof_mark_visualizer_snapshot; - widget_class->size_allocate = sysprof_mark_visualizer_size_allocate; -} - -static void -sysprof_mark_visualizer_init (SysprofMarkVisualizer *self) -{ - self->rgba_by_kind = g_hash_table_new_full (NULL, NULL, NULL, g_free); - self->row_by_kind = g_hash_table_new (NULL, NULL); - self->rgba_by_group = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); -} - -void -sysprof_mark_visualizer_set_group_rgba (SysprofMarkVisualizer *self, - const gchar *group, - const GdkRGBA *rgba) -{ - g_return_if_fail (SYSPROF_IS_MARK_VISUALIZER (self)); - g_return_if_fail (group != NULL); - - g_hash_table_insert (self->rgba_by_group, - g_strdup (group), - g_memdup2 (rgba, sizeof *rgba)); -} - -void -sysprof_mark_visualizer_set_kind_rgba (SysprofMarkVisualizer *self, - GHashTable *rgba_by_kind) -{ - g_return_if_fail (SYSPROF_IS_MARK_VISUALIZER (self)); - - if (rgba_by_kind != self->rgba_by_kind) - { - g_hash_table_remove_all (self->row_by_kind); - - g_clear_pointer (&self->rgba_by_kind, g_hash_table_unref); - - if (rgba_by_kind) - { - GHashTableIter iter; - guint row = 0; - gpointer k; - - self->rgba_by_kind = g_hash_table_ref (rgba_by_kind); - - g_hash_table_iter_init (&iter, rgba_by_kind); - while (g_hash_table_iter_next (&iter, &k, NULL)) - g_hash_table_insert (self->row_by_kind, k, GUINT_TO_POINTER (row++)); - - gtk_widget_set_size_request (GTK_WIDGET (self), - -1, - MAX (35, row * (RECT_HEIGHT - RECT_OVERLAP))); - } - } -} diff --git a/src/libsysprof-ui/sysprof-mark-visualizer.h b/src/libsysprof-ui/sysprof-mark-visualizer.h deleted file mode 100644 index 36327e13..00000000 --- a/src/libsysprof-ui/sysprof-mark-visualizer.h +++ /dev/null @@ -1,47 +0,0 @@ -/* sysprof-mark-visualizer.h - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-visualizer.h" - -G_BEGIN_DECLS - -typedef struct -{ - gint64 begin; - gint64 end; - guint kind; - gint x; - gint x2; -} SysprofMarkTimeSpan; - -#define SYSPROF_TYPE_MARK_VISUALIZER (sysprof_mark_visualizer_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofMarkVisualizer, sysprof_mark_visualizer, SYSPROF, MARK_VISUALIZER, SysprofVisualizer) - -SysprofVisualizer *sysprof_mark_visualizer_new (GHashTable *groups); -void sysprof_mark_visualizer_set_group_rgba (SysprofMarkVisualizer *self, - const gchar *group, - const GdkRGBA *rgba); -void sysprof_mark_visualizer_set_kind_rgba (SysprofMarkVisualizer *self, - GHashTable *rgba_by_kind); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-marks-aid.c b/src/libsysprof-ui/sysprof-marks-aid.c deleted file mode 100644 index 4361dfab..00000000 --- a/src/libsysprof-ui/sysprof-marks-aid.c +++ /dev/null @@ -1,490 +0,0 @@ -/* sysprof-marks-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-marks-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-marks-aid.h" -#include "sysprof-marks-page.h" -#include "sysprof-mark-visualizer.h" - -struct _SysprofMarksAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofDisplay *display; - SysprofCaptureCursor *cursor; - GHashTable *categories; - GHashTable *kinds; - guint last_kind; - guint has_marks : 1; -} Present; - -G_DEFINE_TYPE (SysprofMarksAid, sysprof_marks_aid, SYSPROF_TYPE_AID) - -static void -rgb_to_hls (gdouble *r, - gdouble *g, - gdouble *b) -{ - gdouble min; - gdouble max; - gdouble red; - gdouble green; - gdouble blue; - gdouble h, l, s; - gdouble delta; - - red = *r; - green = *g; - blue = *b; - if (red > green) - { - if (red > blue) - max = red; - else - max = blue; - if (green < blue) - min = green; - else - min = blue; - } - else - { - if (green > blue) - max = green; - else - max = blue; - if (red < blue) - min = red; - else - min = blue; - } - l = (max + min) / 2; - s = 0; - h = 0; - if (max != min) - { - if (l <= 0.5) - s = (max - min) / (max + min); - else - s = (max - min) / (2 - max - min); - delta = max - min; - if (red == max) - h = (green - blue) / delta; - else if (green == max) - h = 2 + (blue - red) / delta; - else if (blue == max) - h = 4 + (red - green) / delta; - h *= 60; - if (h < 0.0) - h += 360; - } - *r = h; - *g = l; - *b = s; -} - -static void -hls_to_rgb (gdouble *h, - gdouble *l, - gdouble *s) -{ - gdouble hue; - gdouble lightness; - gdouble saturation; - gdouble m1, m2; - gdouble r, g, b; - - lightness = *l; - saturation = *s; - if (lightness <= 0.5) - m2 = lightness * (1 + saturation); - else - m2 = lightness + saturation - lightness * saturation; - m1 = 2 * lightness - m2; - if (saturation == 0) - { - *h = lightness; - *l = lightness; - *s = lightness; - } - else - { - hue = *h + 120; - while (hue > 360) - hue -= 360; - while (hue < 0) - hue += 360; - if (hue < 60) - r = m1 + (m2 - m1) * hue / 60; - else if (hue < 180) - r = m2; - else if (hue < 240) - r = m1 + (m2 - m1) * (240 - hue) / 60; - else - r = m1; - hue = *h; - while (hue > 360) - hue -= 360; - while (hue < 0) - hue += 360; - if (hue < 60) - g = m1 + (m2 - m1) * hue / 60; - else if (hue < 180) - g = m2; - else if (hue < 240) - g = m1 + (m2 - m1) * (240 - hue) / 60; - else - g = m1; - hue = *h - 120; - while (hue > 360) - hue -= 360; - while (hue < 0) - hue += 360; - if (hue < 60) - b = m1 + (m2 - m1) * hue / 60; - else if (hue < 180) - b = m2; - else if (hue < 240) - b = m1 + (m2 - m1) * (240 - hue) / 60; - else - b = m1; - *h = r; - *l = g; - *s = b; - } -} - -static void -rgba_shade (const GdkRGBA *rgba, - GdkRGBA *dst, - gdouble k) -{ - gdouble red; - gdouble green; - gdouble blue; - - red = rgba->red; - green = rgba->green; - blue = rgba->blue; - - rgb_to_hls (&red, &green, &blue); - - green *= k; - - if (green > 1.0) - green = 1.0; - else if (green < 0.0) - green = 0.0; - - blue *= k; - - if (blue > 1.0) - blue = 1.0; - else if (blue < 0.0) - blue = 0.0; - - hls_to_rgb (&red, &green, &blue); - - dst->red = red; - dst->green = green; - dst->blue = blue; - dst->alpha = rgba->alpha; -} - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->categories, g_hash_table_unref); - g_clear_pointer (&p->kinds, g_hash_table_unref); - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -static void -on_group_activated_cb (SysprofVisualizerGroup *group, - SysprofPage *page) -{ - SysprofDisplay *display; - - g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); - g_assert (SYSPROF_IS_PAGE (page)); - - display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY)); - sysprof_display_set_visible_page (display, page); -} - -/** - * sysprof_marks_aid_new: - * - * Create a new #SysprofMarksAid. - * - * Returns: (transfer full): a newly created #SysprofMarksAid - */ -SysprofAid * -sysprof_marks_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_MARKS_AID, NULL); -} - -static bool -find_marks_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - Present *p = user_data; - - g_assert (frame != NULL); - g_assert (p != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_MARK) - { - const SysprofCaptureMark *mark = (const SysprofCaptureMark *)frame; - SysprofMarkTimeSpan span = { frame->time, frame->time + mark->duration }; - gchar joined[64]; - gpointer kptr; - GArray *items; - - p->has_marks = TRUE; - - if G_UNLIKELY (!(items = g_hash_table_lookup (p->categories, mark->group))) - { - items = g_array_new (FALSE, FALSE, sizeof (SysprofMarkTimeSpan)); - g_hash_table_insert (p->categories, g_strdup (mark->group), items); - } - - g_snprintf (joined, sizeof joined, "%s:%s", mark->group, mark->name); - - if G_UNLIKELY (!(kptr = g_hash_table_lookup (p->kinds, joined))) - { - p->last_kind++; - kptr = GINT_TO_POINTER (p->last_kind); - g_hash_table_insert (p->kinds, g_strdup (joined), kptr); - } - - span.kind = GPOINTER_TO_INT (kptr); - - g_array_append_val (items, span); - } - - return TRUE; -} - -static gint -compare_span (const SysprofMarkTimeSpan *a, - const SysprofMarkTimeSpan *b) -{ - if (a->kind < b->kind) - return -1; - - if (b->kind < a->kind) - return 1; - - if (a->begin < b->begin) - return -1; - - if (b->begin < a->begin) - return 1; - - if (b->end > a->end) - return -1; - - return 0; -} - -static void -sysprof_marks_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *p = task_data; - GHashTableIter iter; - gpointer k, v; - - g_assert (G_IS_TASK (task)); - g_assert (p != NULL); - g_assert (SYSPROF_IS_DISPLAY (p->display)); - g_assert (p->cursor != NULL); - g_assert (SYSPROF_IS_MARKS_AID (source_object)); - - sysprof_capture_cursor_foreach (p->cursor, find_marks_cb, p); - - g_hash_table_iter_init (&iter, p->categories); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - GArray *spans = v; - - g_array_sort (spans, (GCompareFunc)compare_span); - } - - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_marks_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType marks[] = { - SYSPROF_CAPTURE_FRAME_MARK, - }; - SysprofMarksAid *self = (SysprofMarksAid *)aid; - g_autoptr(GTask) task = NULL; - Present p = {0}; - - g_assert (SYSPROF_IS_MARKS_AID (self)); - - p.display = g_object_ref (display); - p.categories = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify) g_array_unref); - p.kinds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - p.cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (p.cursor, - sysprof_capture_condition_new_where_type_in (1, marks)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_marks_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &p), - present_free); - g_task_run_in_thread (task, sysprof_marks_aid_present_worker); -} - -static gboolean -sysprof_marks_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - Present *p; - - g_assert (SYSPROF_IS_MARKS_AID (aid)); - g_assert (G_IS_TASK (result)); - - p = g_task_get_task_data (G_TASK (result)); - - if (p->has_marks) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - SysprofVisualizerGroup *group; - SysprofVisualizer *marks; - SysprofPage *page; - GHashTableIter iter; - gpointer k, v; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "has-page", TRUE, - "title", _("Timings"), - "visible", TRUE, - NULL); - - marks = sysprof_mark_visualizer_new (p->categories); - sysprof_visualizer_set_title (marks, _("Timings")); - gtk_widget_show (GTK_WIDGET (marks)); - - g_hash_table_iter_init (&iter, p->categories); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - g_autoptr(GHashTable) seen = g_hash_table_new_full (NULL, NULL, NULL, g_free); - g_autoptr(GHashTable) scoped = NULL; - SysprofVisualizer *scoped_vis; - GArray *spans = v; - const gchar *name = k; - GdkRGBA rgba; - GdkRGBA kind_rgba; - gdouble ratio; - - sysprof_color_cycle_next (cycle, &rgba); - sysprof_mark_visualizer_set_group_rgba (SYSPROF_MARK_VISUALIZER (marks), name, &rgba); - - /* Now make a scoped row just for this group */ - scoped = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, - (GDestroyNotify)g_array_unref); - g_hash_table_insert (scoped, g_strdup (name), g_array_ref (spans)); - - scoped_vis = sysprof_mark_visualizer_new (scoped); - sysprof_visualizer_set_title (scoped_vis, name); - sysprof_mark_visualizer_set_group_rgba (SYSPROF_MARK_VISUALIZER (scoped_vis), name, &rgba); - sysprof_visualizer_group_insert (group, scoped_vis, -1, TRUE); - - ratio = .4 / p->last_kind; - - for (guint i = 0; i < spans->len; i++) - { - const SysprofMarkTimeSpan *span = &g_array_index (spans, SysprofMarkTimeSpan, i); - - if (!g_hash_table_contains (seen, GUINT_TO_POINTER (span->kind))) - { - rgba_shade (&rgba, &kind_rgba, 1 + (ratio * span->kind)); - g_hash_table_insert (seen, - GUINT_TO_POINTER (span->kind), - g_memdup2 (&kind_rgba, sizeof kind_rgba)); - } - } - - sysprof_mark_visualizer_set_kind_rgba (SYSPROF_MARK_VISUALIZER (scoped_vis), seen); - } - - page = g_object_new (SYSPROF_TYPE_MARKS_PAGE, - "zoom-manager", sysprof_display_get_zoom_manager (p->display), - "visible", TRUE, - NULL); - - g_signal_connect_object (group, - "group-activated", - G_CALLBACK (on_group_activated_cb), - page, - 0); - - sysprof_visualizer_group_insert (group, marks, 0, FALSE); - sysprof_display_add_group (p->display, group); - sysprof_display_add_page (p->display, page); - } - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_marks_aid_class_init (SysprofMarksAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->present_async = sysprof_marks_aid_present_async; - aid_class->present_finish = sysprof_marks_aid_present_finish; -} - -static void -sysprof_marks_aid_init (SysprofMarksAid *self) -{ -} diff --git a/src/libsysprof-ui/sysprof-marks-model.c b/src/libsysprof-ui/sysprof-marks-model.c deleted file mode 100644 index 08000641..00000000 --- a/src/libsysprof-ui/sysprof-marks-model.c +++ /dev/null @@ -1,592 +0,0 @@ -/* sysprof-marks-model.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-marks-model" - -#include "config.h" - -#include - -#include "sysprof-marks-model.h" - -struct _SysprofMarksModel -{ - GObject parent_instance; - GStringChunk *chunks; - GHashTable *counters; - GArray *items; - gint64 max_end_time; -}; - -typedef struct -{ - gint64 begin_time; - gint64 end_time; - const gchar *group; - const gchar *name; - const gchar *message; - SysprofCaptureCounterValue value; - guint is_counter : 1; - guint counter_type : 8; -} Item; - -static void -counter_free (gpointer data) -{ - g_slice_free (SysprofCaptureCounter, data); -} - -static gint -sysprof_marks_model_get_n_columns (GtkTreeModel *model) -{ - return SYSPROF_MARKS_MODEL_COLUMN_LAST; -} - -static GType -sysprof_marks_model_get_column_type (GtkTreeModel *model, - gint column) -{ - switch (column) - { - case SYSPROF_MARKS_MODEL_COLUMN_GROUP: - return G_TYPE_STRING; - - case SYSPROF_MARKS_MODEL_COLUMN_NAME: - return G_TYPE_STRING; - - case SYSPROF_MARKS_MODEL_COLUMN_BEGIN_TIME: - return G_TYPE_INT64; - - case SYSPROF_MARKS_MODEL_COLUMN_END_TIME: - return G_TYPE_INT64; - - case SYSPROF_MARKS_MODEL_COLUMN_DURATION: - return G_TYPE_DOUBLE; - - case SYSPROF_MARKS_MODEL_COLUMN_TEXT: - return G_TYPE_STRING; - - default: - return 0; - } -} - -static GtkTreePath * -sysprof_marks_model_get_path (GtkTreeModel *model, - GtkTreeIter *iter) -{ - gint off; - - g_assert (SYSPROF_IS_MARKS_MODEL (model)); - g_assert (iter != NULL); - - off = GPOINTER_TO_INT (iter->user_data); - - return gtk_tree_path_new_from_indices (off, -1); -} - -static gboolean -sysprof_marks_model_get_iter (GtkTreeModel *model, - GtkTreeIter *iter, - GtkTreePath *path) -{ - SysprofMarksModel *self = (SysprofMarksModel *)model; - gint off; - - g_assert (SYSPROF_IS_MARKS_MODEL (self)); - g_assert (iter != NULL); - g_assert (path != NULL); - - memset (iter, 0, sizeof *iter); - - if (gtk_tree_path_get_depth (path) != 1) - return FALSE; - - off = gtk_tree_path_get_indices (path)[0]; - iter->user_data = GINT_TO_POINTER (off); - - return off >= 0 && off < self->items->len; -} - -static gboolean -sysprof_marks_model_iter_next (GtkTreeModel *model, - GtkTreeIter *iter) -{ - SysprofMarksModel *self = (SysprofMarksModel *)model; - gint off; - - g_assert (SYSPROF_IS_MARKS_MODEL (self)); - g_assert (iter != NULL); - - off = GPOINTER_TO_INT (iter->user_data); - off++; - iter->user_data = GINT_TO_POINTER (off); - - return off < self->items->len; -} - -static gboolean -sysprof_marks_model_iter_nth_child (GtkTreeModel *model, - GtkTreeIter *iter, - GtkTreeIter *parent, - gint n) -{ - SysprofMarksModel *self = (SysprofMarksModel *)model; - - g_assert (SYSPROF_IS_MARKS_MODEL (self)); - g_assert (iter != NULL); - - if (parent != NULL) - return FALSE; - - iter->user_data = GINT_TO_POINTER (n); - - return n < self->items->len; -} - -static gint -sysprof_marks_model_iter_n_children (GtkTreeModel *model, - GtkTreeIter *iter) -{ - SysprofMarksModel *self = (SysprofMarksModel *)model; - - g_assert (SYSPROF_IS_MARKS_MODEL (self)); - - return iter ? 0 : self->items->len; -} - -static gboolean -sysprof_marks_model_iter_has_child (GtkTreeModel *model, - GtkTreeIter *iter) -{ - return FALSE; -} - -static GtkTreeModelFlags -sysprof_marks_model_get_flags (GtkTreeModel *model) -{ - return GTK_TREE_MODEL_LIST_ONLY; -} - -static void -sysprof_marks_model_get_value (GtkTreeModel *model, - GtkTreeIter *iter, - gint column, - GValue *value) -{ - SysprofMarksModel *self = (SysprofMarksModel *)model; - const Item *item; - - g_assert (SYSPROF_IS_MARKS_MODEL (self)); - g_assert (iter != NULL); - g_assert (column < SYSPROF_MARKS_MODEL_COLUMN_LAST); - - item = &g_array_index (self->items, Item, GPOINTER_TO_INT (iter->user_data)); - - switch (column) - { - case SYSPROF_MARKS_MODEL_COLUMN_GROUP: - g_value_init (value, G_TYPE_STRING); - g_value_set_string (value, item->group); - break; - - case SYSPROF_MARKS_MODEL_COLUMN_NAME: - g_value_init (value, G_TYPE_STRING); - g_value_set_string (value, item->name); - break; - - case SYSPROF_MARKS_MODEL_COLUMN_BEGIN_TIME: - g_value_init (value, G_TYPE_INT64); - g_value_set_int64 (value, item->begin_time); - break; - - case SYSPROF_MARKS_MODEL_COLUMN_END_TIME: - g_value_init (value, G_TYPE_INT64); - g_value_set_int64 (value, item->end_time); - break; - - case SYSPROF_MARKS_MODEL_COLUMN_DURATION: - g_value_init (value, G_TYPE_DOUBLE); - if (item->end_time) - g_value_set_double (value, (item->end_time - item->begin_time) / (double)(G_USEC_PER_SEC * 1000)); - break; - - case SYSPROF_MARKS_MODEL_COLUMN_TEXT: - g_value_init (value, G_TYPE_STRING); - if (item->is_counter) - { - gchar *val = NULL; - - if (item->counter_type == SYSPROF_CAPTURE_COUNTER_DOUBLE) - val = g_strdup_printf ("%s — %s = %.4lf", item->group, item->name, item->value.vdbl); - else if (item->counter_type == SYSPROF_CAPTURE_COUNTER_INT64) - val = g_strdup_printf ("%s — %s = %"G_GINT64_FORMAT, item->group, item->name, item->value.v64); - - g_value_take_string (value, g_steal_pointer (&val)); - } - else - { - if (item->message && item->message[0]) - g_value_take_string (value, g_strdup_printf ("%s — %s", item->name, item->message)); - else - g_value_set_string (value, item->name); - } - break; - - default: - break; - } -} - -static void -tree_model_iface_init (GtkTreeModelIface *iface) -{ - iface->get_n_columns = sysprof_marks_model_get_n_columns; - iface->get_column_type = sysprof_marks_model_get_column_type; - iface->get_iter = sysprof_marks_model_get_iter; - iface->get_path = sysprof_marks_model_get_path; - iface->iter_next = sysprof_marks_model_iter_next; - iface->iter_n_children = sysprof_marks_model_iter_n_children; - iface->iter_nth_child = sysprof_marks_model_iter_nth_child; - iface->iter_has_child = sysprof_marks_model_iter_has_child; - iface->get_flags = sysprof_marks_model_get_flags; - iface->get_value = sysprof_marks_model_get_value; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofMarksModel, sysprof_marks_model, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, tree_model_iface_init)) - -static void -sysprof_marks_model_finalize (GObject *object) -{ - SysprofMarksModel *self = (SysprofMarksModel *)object; - - g_clear_pointer (&self->counters, g_hash_table_unref); - g_clear_pointer (&self->items, g_array_unref); - g_clear_pointer (&self->chunks, g_string_chunk_free); - - G_OBJECT_CLASS (sysprof_marks_model_parent_class)->finalize (object); -} - -static void -sysprof_marks_model_class_init (SysprofMarksModelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_marks_model_finalize; -} - -static void -sysprof_marks_model_init (SysprofMarksModel *self) -{ - self->counters = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, counter_free); - self->chunks = g_string_chunk_new (4096*16); - self->items = g_array_new (FALSE, FALSE, sizeof (Item)); -} - -static bool -cursor_foreach_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofMarksModel *self = user_data; - Item item; - - g_assert (SYSPROF_IS_MARKS_MODEL (self)); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_MARK || - frame->type == SYSPROF_CAPTURE_FRAME_CTRSET || - frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF || - frame->type == SYSPROF_CAPTURE_FRAME_FORK); - - if (frame->type == SYSPROF_CAPTURE_FRAME_MARK) - { - SysprofCaptureMark *mark = (SysprofCaptureMark *)frame; - - item.begin_time = frame->time; - item.end_time = item.begin_time + mark->duration; - item.group = g_string_chunk_insert_const (self->chunks, mark->group); - item.name = g_string_chunk_insert_const (self->chunks, mark->name); - item.message = g_string_chunk_insert_const (self->chunks, mark->message); - item.value.v64 = 0; - item.is_counter = FALSE; - item.counter_type = 0; - - if G_LIKELY (item.end_time > self->max_end_time) - self->max_end_time = item.end_time; - - g_array_append_val (self->items, item); - } - else if (frame->type == SYSPROF_CAPTURE_FRAME_FORK) - { - SysprofCaptureFork *fk = (SysprofCaptureFork *)frame; - g_autofree gchar *message = g_strdup_printf ("PID: %d, Child PID: %d", frame->pid, fk->child_pid); - - item.begin_time = frame->time; - item.end_time = item.begin_time; - item.group = g_string_chunk_insert_const (self->chunks, "fork"); - item.name = g_string_chunk_insert_const (self->chunks, "Fork"); - item.message = g_string_chunk_insert_const (self->chunks, message); - item.value.v64 = 0; - item.is_counter = FALSE; - item.counter_type = 0; - - g_array_append_val (self->items, item); - } - else if (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF) - { - SysprofCaptureCounterDefine *ctrdef = (SysprofCaptureCounterDefine *)frame; - - for (guint i = 0; i < ctrdef->n_counters; i++) - { - SysprofCaptureCounter *ctr = &ctrdef->counters[i]; - - g_hash_table_insert (self->counters, - GUINT_TO_POINTER ((guint)ctr->id), - g_slice_dup (SysprofCaptureCounter, ctr)); - } - } - else if (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET) - { - SysprofCaptureCounterSet *ctrset = (SysprofCaptureCounterSet *)frame; - - for (guint i = 0; i < ctrset->n_values; i++) - { - SysprofCaptureCounterValues *values = &ctrset->values[i]; - - for (guint j = 0; j < G_N_ELEMENTS (values->ids); j++) - { - guint32 id = values->ids[j]; - SysprofCaptureCounter *ctr = NULL; - - if (id == 0) - break; - - if ((ctr = g_hash_table_lookup (self->counters, GUINT_TO_POINTER (id)))) - { - item.begin_time = frame->time; - item.end_time = frame->time; - item.group = ctr->category; - item.name = ctr->name; - item.message = NULL; - item.is_counter = TRUE; - item.counter_type = ctr->type; - - memcpy (&item.value, &values->values[j], sizeof item.value); - - g_array_append_val (self->items, item); - } - } - - } - } - - return TRUE; -} - -static gint -item_compare (gconstpointer a, - gconstpointer b) -{ - const Item *ia = a; - const Item *ib = b; - - if (ia->begin_time < ib->begin_time) - return -1; - else if (ia->begin_time > ib->begin_time) - return 1; - - /* Sort items with longer duration first, as they might be - * "overarching" marks containing other marks. - */ - if (ia->end_time > ib->end_time) - return -1; - else if (ib->end_time > ia->end_time) - return 1; - - return 0; -} - -static void -sysprof_marks_model_new_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - g_autoptr(SysprofMarksModel) self = NULL; - SysprofCaptureCursor *cursor = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - self = g_object_new (SYSPROF_TYPE_MARKS_MODEL, NULL); - sysprof_capture_cursor_foreach (cursor, cursor_foreach_cb, self); - g_array_sort (self->items, item_compare); - - g_task_return_pointer (task, g_steal_pointer (&self), g_object_unref); -} - -static void -sysprof_marks_model_selection_foreach_cb (SysprofSelection *selection, - gint64 begin, - gint64 end, - gpointer user_data) -{ - SysprofCaptureCondition **condition = user_data; - SysprofCaptureCondition *c; - - g_assert (SYSPROF_IS_SELECTION (selection)); - g_assert (condition != NULL); - - c = sysprof_capture_condition_new_where_time_between (begin, end); - - if (*condition != NULL) - *condition = sysprof_capture_condition_new_or (g_steal_pointer (&c), - g_steal_pointer (condition)); - else - *condition = g_steal_pointer (&c); -} - -void -sysprof_marks_model_new_async (SysprofCaptureReader *reader, - SysprofMarksModelKind kind, - SysprofSelection *selection, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType ctrset[] = { SYSPROF_CAPTURE_FRAME_CTRDEF }; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - SysprofCaptureCondition *c; - - g_return_if_fail (reader != NULL); - g_return_if_fail (!selection || SYSPROF_IS_SELECTION (selection)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - cursor = sysprof_capture_cursor_new (reader); - - if (kind == SYSPROF_MARKS_MODEL_BOTH) - { - static const SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_CTRSET, - SYSPROF_CAPTURE_FRAME_MARK, - }; - - c = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - } - else if (kind == SYSPROF_MARKS_MODEL_MARKS) - { - static const SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_MARK, - SYSPROF_CAPTURE_FRAME_FORK, - }; - - c = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - } - else if (kind == SYSPROF_MARKS_MODEL_COUNTERS) - { - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_CTRSET }; - - c = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - } - else - { - g_task_report_new_error (NULL, callback, user_data, - sysprof_marks_model_new_async, - G_IO_ERROR, - G_IO_ERROR_INVAL, - "Invalid arguments"); - return; - } - - if (selection) - { - SysprofCaptureCondition *condition = NULL; - - sysprof_selection_foreach (selection, - sysprof_marks_model_selection_foreach_cb, - &condition); - if (condition) - c = sysprof_capture_condition_new_and (c, g_steal_pointer (&condition)); - } - - if (kind & SYSPROF_MARKS_MODEL_COUNTERS) - { - c = sysprof_capture_condition_new_or ( - sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (ctrset), ctrset), - g_steal_pointer (&c)); - } - - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&c)); - - task = g_task_new (NULL, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_marks_model_new_async); - g_task_set_task_data (task, - g_steal_pointer (&cursor), - (GDestroyNotify) sysprof_capture_cursor_unref); - g_task_run_in_thread (task, sysprof_marks_model_new_worker); -} - -SysprofMarksModel * -sysprof_marks_model_new_finish (GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (G_IS_TASK (result), NULL); - - return g_task_propagate_pointer (G_TASK (result), error); -} - -void -sysprof_marks_model_get_range (SysprofMarksModel *self, - gint64 *begin_time, - gint64 *end_time) -{ - g_return_if_fail (SYSPROF_IS_MARKS_MODEL (self)); - - if (begin_time != NULL) - { - *begin_time = 0; - - if (self->items->len > 0) - *begin_time = g_array_index (self->items, Item, 0).begin_time; - } - - if (end_time != NULL) - *end_time = self->max_end_time; -} - -GType -sysprof_marks_model_kind_get_type (void) -{ - static GType type_id; - - if (g_once_init_enter (&type_id)) - { - static const GEnumValue values[] = { - { SYSPROF_MARKS_MODEL_MARKS, "SYSPROF_MARKS_MODEL_MARKS", "marks" }, - { SYSPROF_MARKS_MODEL_COUNTERS, "SYSPROF_MARKS_MODEL_COUNTERS", "counters" }, - { SYSPROF_MARKS_MODEL_BOTH, "SYSPROF_MARKS_MODEL_BOTH", "both" }, - { 0 }, - }; - GType _type_id = g_enum_register_static ("SysprofMarksModelKind", values); - g_once_init_leave (&type_id, _type_id); - } - - return type_id; -} diff --git a/src/libsysprof-ui/sysprof-marks-model.h b/src/libsysprof-ui/sysprof-marks-model.h deleted file mode 100644 index 203ad8ec..00000000 --- a/src/libsysprof-ui/sysprof-marks-model.h +++ /dev/null @@ -1,65 +0,0 @@ -/* sysprof-marks-model.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MARKS_MODEL (sysprof_marks_model_get_type()) -#define SYSPROF_TYPE_MARKS_MODEL_KIND (sysprof_marks_model_kind_get_type()) - -typedef enum -{ - SYSPROF_MARKS_MODEL_COLUMN_GROUP, - SYSPROF_MARKS_MODEL_COLUMN_NAME, - SYSPROF_MARKS_MODEL_COLUMN_BEGIN_TIME, - SYSPROF_MARKS_MODEL_COLUMN_END_TIME, - SYSPROF_MARKS_MODEL_COLUMN_DURATION, - SYSPROF_MARKS_MODEL_COLUMN_TEXT, -} SysprofMarksModelColumn; - -typedef enum -{ - SYSPROF_MARKS_MODEL_MARKS = 1, - SYSPROF_MARKS_MODEL_COUNTERS, - SYSPROF_MARKS_MODEL_BOTH = SYSPROF_MARKS_MODEL_MARKS | SYSPROF_MARKS_MODEL_COUNTERS, -} SysprofMarksModelKind; - -#define SYSPROF_MARKS_MODEL_COLUMN_LAST (SYSPROF_MARKS_MODEL_COLUMN_TEXT+1) - -G_DECLARE_FINAL_TYPE (SysprofMarksModel, sysprof_marks_model, SYSPROF, MARKS_MODEL, GObject) - -GType sysprof_marks_model_kind_get_type (void) G_GNUC_CONST; -void sysprof_marks_model_new_async (SysprofCaptureReader *reader, - SysprofMarksModelKind kind, - SysprofSelection *selection, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -SysprofMarksModel *sysprof_marks_model_new_finish (GAsyncResult *result, - GError **error); -void sysprof_marks_model_get_range (SysprofMarksModel *self, - gint64 *begin_time, - gint64 *end_time); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-marks-page.c b/src/libsysprof-ui/sysprof-marks-page.c deleted file mode 100644 index c08381b9..00000000 --- a/src/libsysprof-ui/sysprof-marks-page.c +++ /dev/null @@ -1,610 +0,0 @@ -/* sysprof-marks-page.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-marks-page" - -#include "config.h" - -#include "sysprof-cell-renderer-duration.h" -#include "sysprof-marks-model.h" -#include "sysprof-marks-page.h" -#include "sysprof-ui-private.h" -#include "sysprof-zoom-manager.h" - -typedef struct -{ - SysprofMarksModelKind kind; - - SysprofZoomManager *zoom_manager; - - gint64 capture_begin_time; - gint64 capture_end_time; - - /* Template objects */ - GtkScrolledWindow *scroller; - GtkTreeView *tree_view; - GtkBox *details_box; - GtkTreeViewColumn *duration_column; - SysprofCellRendererDuration *duration_cell; - GtkStack *stack; - GtkLabel *group; - GtkLabel *mark; - GtkLabel *time; - GtkLabel *end; - GtkLabel *duration; - GtkTextView *message; - GtkWidget *failed; - GtkWidget *marks; -} SysprofMarksPagePrivate; - -enum { - PROP_0, - PROP_KIND, - PROP_ZOOM_MANAGER, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofMarksPage, sysprof_marks_page, SYSPROF_TYPE_PAGE) - -static gboolean -sysprof_marks_page_tree_view_key_press_event_cb (SysprofMarksPage *self, - guint keyval, - guint keycode, - GdkModifierType state, - GtkEventControllerKey *controller) -{ - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - gint dir = 0; - - g_assert (SYSPROF_MARKS_PAGE (self)); - g_assert (GTK_IS_EVENT_CONTROLLER_KEY (controller)); - - if ((state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK|GDK_ALT_MASK)) == 0) - { - switch (keyval) - { - case GDK_KEY_Left: - dir = -1; - break; - - case GDK_KEY_Right: - dir = 1; - break; - - default: - break; - } - - if (dir) - { - GtkAdjustment *adj = gtk_scrolled_window_get_hadjustment (priv->scroller); - gdouble step = gtk_adjustment_get_step_increment (adj); - gdouble val = CLAMP (gtk_adjustment_get_value (adj) + (step * dir), - gtk_adjustment_get_lower (adj), - gtk_adjustment_get_upper (adj)); - gtk_adjustment_set_value (adj, val); - return GDK_EVENT_STOP; - } - } - - return GDK_EVENT_PROPAGATE; -} - -static gboolean -get_first_selected (GtkTreeSelection *selection, - GtkTreeModel **model, - GtkTreeIter *iter) -{ - GtkTreeModel *m; - - g_assert (GTK_IS_TREE_SELECTION (selection)); - - if (gtk_tree_selection_count_selected_rows (selection) != 1) - return FALSE; - - m = gtk_tree_view_get_model (gtk_tree_selection_get_tree_view (selection)); - if (model) - *model = m; - - if (iter) - { - GList *paths = gtk_tree_selection_get_selected_rows (selection, model); - gtk_tree_model_get_iter (m, iter, paths->data); - g_list_free_full (paths, (GDestroyNotify)gtk_tree_path_free); - } - - return TRUE; -} - -static void -sysprof_marks_page_selection_changed_cb (SysprofMarksPage *self, - GtkTreeSelection *selection) -{ - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - GtkTreeModel *model; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_MARKS_PAGE (self)); - g_assert (GTK_IS_TREE_SELECTION (selection)); - - if (get_first_selected (selection, &model, &iter)) - { - g_autofree gchar *group = NULL; - g_autofree gchar *name = NULL; - g_autofree gchar *duration_str = NULL; - g_autofree gchar *time_str = NULL; - g_autofree gchar *end_str = NULL; - g_autofree gchar *text = NULL; - GtkAdjustment *adj; - gdouble x; - gint64 begin_time; - gint64 end_time; - gint64 duration; - gint64 etime; - gint64 otime; - gdouble lower; - gdouble upper; - gdouble value; - gdouble page_size; - gint width; - - gtk_tree_model_get (model, &iter, - SYSPROF_MARKS_MODEL_COLUMN_GROUP, &group, - SYSPROF_MARKS_MODEL_COLUMN_NAME, &name, - SYSPROF_MARKS_MODEL_COLUMN_BEGIN_TIME, &begin_time, - SYSPROF_MARKS_MODEL_COLUMN_END_TIME, &end_time, - SYSPROF_MARKS_MODEL_COLUMN_TEXT, &text, - -1); - - duration = end_time - begin_time; - duration_str = _sysprof_format_duration (duration); - - otime = begin_time - priv->capture_begin_time; - time_str = _sysprof_format_duration (otime); - - etime = end_time - priv->capture_begin_time; - end_str = _sysprof_format_duration (etime); - - gtk_label_set_label (priv->group, group); - gtk_label_set_label (priv->mark, name); - gtk_label_set_label (priv->duration, duration_str); - gtk_label_set_label (priv->time, time_str); - gtk_label_set_label (priv->end, end_str); - - gtk_text_buffer_set_text (gtk_text_view_get_buffer (priv->message), text, -1); - - adj = gtk_scrolled_window_get_hadjustment (priv->scroller); - width = gtk_tree_view_column_get_width (priv->duration_column); - x = sysprof_zoom_manager_get_offset_at_time (priv->zoom_manager, - begin_time - priv->capture_begin_time, - width); - - g_object_get (adj, - "lower", &lower, - "upper", &upper, - "value", &value, - "page-size", &page_size, - NULL); - - if (x < value) - gtk_adjustment_set_value (adj, MAX (lower, x - (page_size / 3.0))); - else if (x > (value + page_size)) - gtk_adjustment_set_value (adj, MIN (upper - page_size, (x - (page_size / 3.0)))); - } -} - -static gboolean -sysprof_marks_page_tree_view_query_tooltip_cb (SysprofMarksPage *self, - gint x, - gint y, - gboolean keyboard_mode, - GtkTooltip *tooltip, - GtkTreeView *tree_view) -{ - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - GtkTreeViewColumn *column; - GtkTreePath *path = NULL; - gint cell_x, cell_y; - gboolean ret = FALSE; - - g_assert (SYSPROF_IS_MARKS_PAGE (self)); - g_assert (GTK_IS_TOOLTIP (tooltip)); - g_assert (GTK_IS_TREE_VIEW (tree_view)); - - if (gtk_tree_view_get_path_at_pos (tree_view, x, y, &path, &column, &cell_x, &cell_y)) - { - GtkTreeModel *model = gtk_tree_view_get_model (tree_view); - GtkTreeIter iter; - - if (gtk_tree_model_get_iter (model, &iter, path)) - { - g_autofree gchar *text = NULL; - g_autofree gchar *timestr = NULL; - g_autofree gchar *tooltip_text = NULL; - g_autofree gchar *durationstr = NULL; - gint64 begin_time; - gint64 end_time; - gint64 duration; - - gtk_tree_model_get (model, &iter, - SYSPROF_MARKS_MODEL_COLUMN_BEGIN_TIME, &begin_time, - SYSPROF_MARKS_MODEL_COLUMN_END_TIME, &end_time, - SYSPROF_MARKS_MODEL_COLUMN_TEXT, &text, - -1); - - duration = end_time - begin_time; - begin_time -= priv->capture_begin_time; - durationstr = _sysprof_format_duration (duration); - - if (duration != 0) - timestr = g_strdup_printf ("%0.4lf (%s)", begin_time / (gdouble)SYSPROF_NSEC_PER_SEC, durationstr); - else - timestr = g_strdup_printf ("%0.4lf", begin_time / (gdouble)SYSPROF_NSEC_PER_SEC); - - tooltip_text = g_strdup_printf ("%s: %s", timestr, text); - - gtk_tooltip_set_text (tooltip, tooltip_text); - - ret = TRUE; - } - } - - gtk_tree_path_free (path); - - return ret; -} - -static void -sysprof_marks_page_load_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(SysprofMarksModel) model = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - SysprofMarksPagePrivate *priv; - SysprofCaptureReader *reader; - SysprofMarksPage *self; - - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - self = g_task_get_source_object (task); - priv = sysprof_marks_page_get_instance_private (self); - - if (!(model = sysprof_marks_model_new_finish (result, &error))) - { - g_task_return_error (task, g_steal_pointer (&error)); - return; - } - - reader = g_task_get_task_data (task); - g_assert (reader != NULL); - - priv->capture_begin_time = sysprof_capture_reader_get_start_time (reader); - priv->capture_end_time = sysprof_capture_reader_get_end_time (reader); - - g_object_set (priv->duration_cell, - "capture-begin-time", priv->capture_begin_time, - "capture-end-time", priv->capture_end_time, - "zoom-manager", priv->zoom_manager, - NULL); - - gtk_tree_view_set_model (priv->tree_view, GTK_TREE_MODEL (model)); - - if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (model), NULL) == 0) - gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (priv->failed)); - else - gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (priv->marks)); - - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_marks_page_load_async (SysprofPage *page, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *filter, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofMarksPage *self = (SysprofMarksPage *)page; - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_MARKS_PAGE (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (!selection || SYSPROF_IS_SELECTION (selection)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_marks_page_load_async); - g_task_set_task_data (task, - sysprof_capture_reader_ref (reader), - (GDestroyNotify) sysprof_capture_reader_unref); - - sysprof_marks_model_new_async (reader, - priv->kind, - selection, - cancellable, - sysprof_marks_page_load_cb, - g_steal_pointer (&task)); -} - -static gboolean -sysprof_marks_page_load_finish (SysprofPage *page, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_MARKS_PAGE (page), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_marks_page_set_hadjustment (SysprofPage *page, - GtkAdjustment *hadjustment) -{ - SysprofMarksPage *self = (SysprofMarksPage *)page; - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - - g_assert (SYSPROF_IS_MARKS_PAGE (self)); - g_assert (!hadjustment || GTK_IS_ADJUSTMENT (hadjustment)); - - gtk_scrolled_window_set_hadjustment (priv->scroller, hadjustment); -} - -static void -sysprof_marks_page_set_size_group (SysprofPage *page, - GtkSizeGroup *size_group) -{ - SysprofMarksPage *self = (SysprofMarksPage *)page; - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - - g_assert (SYSPROF_IS_MARKS_PAGE (self)); - g_assert (GTK_IS_SIZE_GROUP (size_group)); - - gtk_size_group_add_widget (size_group, GTK_WIDGET (priv->details_box)); -} - -static void -sysprof_marks_page_tree_view_row_activated_cb (SysprofMarksPage *self, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkTreeView *tree_view) -{ - GtkTreeModel *model; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_MARKS_PAGE (self)); - g_assert (path != NULL); - g_assert (GTK_IS_TREE_VIEW_COLUMN (column)); - g_assert (GTK_IS_TREE_VIEW (tree_view)); - - model = gtk_tree_view_get_model (tree_view); - - if (gtk_tree_model_get_iter (model, &iter, path)) - { - SysprofDisplay *display; - gint64 begin_time; - gint64 end_time; - - gtk_tree_model_get (model, &iter, - SYSPROF_MARKS_MODEL_COLUMN_BEGIN_TIME, &begin_time, - SYSPROF_MARKS_MODEL_COLUMN_END_TIME, &end_time, - -1); - - display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (self), SYSPROF_TYPE_DISPLAY)); - sysprof_display_add_to_selection (display, begin_time, end_time); - } -} - -static void -sysprof_marks_page_finalize (GObject *object) -{ - SysprofMarksPage *self = (SysprofMarksPage *)object; - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - - g_clear_object (&priv->zoom_manager); - - G_OBJECT_CLASS (sysprof_marks_page_parent_class)->finalize (object); -} - -static void -sysprof_marks_page_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofMarksPage *self = SYSPROF_MARKS_PAGE (object); - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - - switch (prop_id) - { - case PROP_KIND: - g_value_set_enum (value, priv->kind); - break; - - case PROP_ZOOM_MANAGER: - g_value_set_object (value, priv->zoom_manager); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_marks_page_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofMarksPage *self = SYSPROF_MARKS_PAGE (object); - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - - switch (prop_id) - { - case PROP_KIND: - priv->kind = g_value_get_enum (value); - break; - - case PROP_ZOOM_MANAGER: - if (g_set_object (&priv->zoom_manager, g_value_get_object (value))) - { - g_object_set (priv->duration_cell, - "zoom-manager", priv->zoom_manager, - NULL); - if (priv->zoom_manager) - g_signal_connect_object (priv->zoom_manager, - "notify::zoom", - G_CALLBACK (gtk_tree_view_column_queue_resize), - priv->duration_column, - G_CONNECT_SWAPPED); - } - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_marks_page_class_init (SysprofMarksPageClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofPageClass *page_class = SYSPROF_PAGE_CLASS (klass); - - object_class->finalize = sysprof_marks_page_finalize; - object_class->get_property = sysprof_marks_page_get_property; - object_class->set_property = sysprof_marks_page_set_property; - - page_class->load_async = sysprof_marks_page_load_async; - page_class->load_finish = sysprof_marks_page_load_finish; - page_class->set_hadjustment = sysprof_marks_page_set_hadjustment; - page_class->set_size_group = sysprof_marks_page_set_size_group; - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-marks-page.ui"); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, end); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, details_box); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, duration_cell); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, duration_column); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, scroller); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, stack); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, tree_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, group); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, mark); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, duration); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, time); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, message); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, marks); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMarksPage, failed); - - properties [PROP_KIND] = - g_param_spec_enum ("kind", NULL, NULL, - SYSPROF_TYPE_MARKS_MODEL_KIND, - SYSPROF_MARKS_MODEL_MARKS, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_ZOOM_MANAGER] = - g_param_spec_object ("zoom-manager", NULL, NULL, - SYSPROF_TYPE_ZOOM_MANAGER, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - g_type_ensure (SYSPROF_TYPE_CELL_RENDERER_DURATION); -} - -static void -sysprof_marks_page_init (SysprofMarksPage *self) -{ - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - GtkEventController *controller; - - priv->kind = SYSPROF_MARKS_MODEL_MARKS; - - gtk_widget_init_template (GTK_WIDGET (self)); - - gtk_tree_selection_set_mode (gtk_tree_view_get_selection (priv->tree_view), - GTK_SELECTION_MULTIPLE); - - controller = gtk_event_controller_key_new (); - gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); - g_signal_connect_object (controller, - "key-pressed", - G_CALLBACK (sysprof_marks_page_tree_view_key_press_event_cb), - self, - G_CONNECT_SWAPPED); - gtk_widget_add_controller (GTK_WIDGET (self), controller); - - g_signal_connect_object (priv->tree_view, - "row-activated", - G_CALLBACK (sysprof_marks_page_tree_view_row_activated_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->tree_view, - "query-tooltip", - G_CALLBACK (sysprof_marks_page_tree_view_query_tooltip_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (gtk_tree_view_get_selection (priv->tree_view), - "changed", - G_CALLBACK (sysprof_marks_page_selection_changed_cb), - self, - G_CONNECT_SWAPPED); -} - -GtkWidget * -sysprof_marks_page_new (SysprofZoomManager *zoom_manager, - SysprofMarksModelKind kind) -{ - SysprofMarksPage *self; - SysprofMarksPagePrivate *priv; - - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (zoom_manager), NULL); - - self = g_object_new (SYSPROF_TYPE_MARKS_PAGE, - "zoom-manager", zoom_manager, - NULL); - priv = sysprof_marks_page_get_instance_private (self); - priv->kind = kind; - - return GTK_WIDGET (self); -} - -void -_sysprof_marks_page_set_hadjustment (SysprofMarksPage *self, - GtkAdjustment *hadjustment) -{ - SysprofMarksPagePrivate *priv = sysprof_marks_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_MARKS_PAGE (self)); - g_return_if_fail (GTK_IS_ADJUSTMENT (hadjustment)); - - gtk_scrolled_window_set_hadjustment (priv->scroller, hadjustment); -} diff --git a/src/libsysprof-ui/sysprof-marks-page.ui b/src/libsysprof-ui/sysprof-marks-page.ui deleted file mode 100644 index 49936079..00000000 --- a/src/libsysprof-ui/sysprof-marks-page.ui +++ /dev/null @@ -1,259 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-memory-aid.c b/src/libsysprof-ui/sysprof-memory-aid.c deleted file mode 100644 index 67341ace..00000000 --- a/src/libsysprof-ui/sysprof-memory-aid.c +++ /dev/null @@ -1,70 +0,0 @@ -/* sysprof-memory-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-memory-aid" - -#include "config.h" - -#include - -#include "sysprof-memory-aid.h" - -struct _SysprofMemoryAid -{ - SysprofAid parent_instance; -}; - -G_DEFINE_TYPE (SysprofMemoryAid, sysprof_memory_aid, SYSPROF_TYPE_AID) - -SysprofAid * -sysprof_memory_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_MEMORY_AID, NULL); -} - -static void -sysprof_memory_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ -#ifdef __linux__ - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_MEMORY_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_memory_source_new (); - sysprof_profiler_add_source (profiler, source); -#endif -} - -static void -sysprof_memory_aid_class_init (SysprofMemoryAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_memory_aid_prepare; -} - -static void -sysprof_memory_aid_init (SysprofMemoryAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Memory Usage")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-memory-aid.h b/src/libsysprof-ui/sysprof-memory-aid.h deleted file mode 100644 index d33a1d8c..00000000 --- a/src/libsysprof-ui/sysprof-memory-aid.h +++ /dev/null @@ -1,33 +0,0 @@ -/* sysprof-memory-aid.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-aid.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MEMORY_AID (sysprof_memory_aid_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofMemoryAid, sysprof_memory_aid, SYSPROF, MEMORY_AID, SysprofAid) - -SysprofAid *sysprof_memory_aid_new (void); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-memprof-aid.c b/src/libsysprof-ui/sysprof-memprof-aid.c deleted file mode 100644 index 21325177..00000000 --- a/src/libsysprof-ui/sysprof-memprof-aid.c +++ /dev/null @@ -1,226 +0,0 @@ -/* sysprof-memprof-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-memprof-aid" - -#include "config.h" - -#include - -#include "sysprof-memprof-aid.h" -#include "sysprof-memprof-page.h" -#include "sysprof-memprof-source.h" -#include "sysprof-memprof-visualizer.h" - -struct _SysprofMemprofAid -{ - SysprofAid parent_instance; -}; - -G_DEFINE_TYPE (SysprofMemprofAid, sysprof_memprof_aid, SYSPROF_TYPE_AID) - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; - guint has_allocs : 1; -} Present; - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -static void -on_group_activated_cb (SysprofVisualizerGroup *group, - SysprofPage *page) -{ - SysprofDisplay *display; - - g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); - g_assert (SYSPROF_IS_PAGE (page)); - - display = SYSPROF_DISPLAY (gtk_widget_get_ancestor (GTK_WIDGET (page), SYSPROF_TYPE_DISPLAY)); - sysprof_display_set_visible_page (display, page); -} - -SysprofAid * -sysprof_memprof_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_MEMPROF_AID, NULL); -} - -static void -sysprof_memprof_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ -#ifdef __linux__ - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_MEMPROF_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_memprof_source_new (); - sysprof_profiler_add_source (profiler, source); -#endif -} - -static bool -discover_samples_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - Present *p = user_data; - - g_assert (frame != NULL); - g_assert (p != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - p->has_allocs = TRUE; - return FALSE; - } - - return TRUE; -} - -static void -sysprof_memprof_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *p = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_MEMPROF_AID (source_object)); - g_assert (p != NULL); - g_assert (p->cursor != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - sysprof_capture_cursor_foreach (p->cursor, discover_samples_cb, p); - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_memprof_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_ALLOCATION }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_MEMPROF_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (1, types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_memprof_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_memprof_aid_present_worker); -} - -static gboolean -sysprof_memprof_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - Present *p; - - g_assert (SYSPROF_IS_MEMPROF_AID (aid)); - g_assert (G_IS_TASK (result)); - - p = g_task_get_task_data (G_TASK (result)); - - if (p->has_allocs) - { - SysprofVisualizerGroup *group; - SysprofVisualizer *row; - SysprofPage *page; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "has-page", TRUE, - "priority", -300, - "title", _("Memory"), - "visible", TRUE, - NULL); - - row = sysprof_memprof_visualizer_new (FALSE); - sysprof_visualizer_group_insert (group, row, 0, FALSE); - - row = sysprof_memprof_visualizer_new (TRUE); - sysprof_visualizer_group_insert (group, row, 1, FALSE); - - page = g_object_new (SYSPROF_TYPE_MEMPROF_PAGE, - "title", _("Memory Allocations"), - "vexpand", TRUE, - "visible", TRUE, - NULL); - sysprof_display_add_page (p->display, page); - - g_signal_connect_object (group, - "group-activated", - G_CALLBACK (on_group_activated_cb), - page, - 0); - - sysprof_display_add_group (p->display, group); - } - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_memprof_aid_class_init (SysprofMemprofAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_memprof_aid_prepare; - aid_class->present_async = sysprof_memprof_aid_present_async; - aid_class->present_finish = sysprof_memprof_aid_present_finish; -} - -static void -sysprof_memprof_aid_init (SysprofMemprofAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Track Allocations")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "org.gnome.Sysprof-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-memprof-page.c b/src/libsysprof-ui/sysprof-memprof-page.c deleted file mode 100644 index 00e98961..00000000 --- a/src/libsysprof-ui/sysprof-memprof-page.c +++ /dev/null @@ -1,1552 +0,0 @@ -/* sysprof-memprof-page.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, 2006, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include - -#include "../stackstash.h" - -#include "egg-paned-private.h" - -#include "sysprof-cell-renderer-percent.h" -#include "sysprof-memprof-page.h" -#include "sysprof-profile.h" - -typedef struct -{ - SysprofMemprofProfile *profile; - - GtkTreeView *callers_view; - GtkTreeView *functions_view; - GtkTreeView *descendants_view; - GtkTreeViewColumn *descendants_name_column; - GtkTreeViewColumn *function_size_column; - GtkCellRendererText *function_size_cell; - GtkStack *stack; - GtkToggleButton *summary; - GtkToggleButton *all_allocs; - GtkToggleButton *temp_allocs; - GtkToggleButton *leaked_allocs_button; - GtkLabel *temp_allocs_count; - GtkLabel *num_allocs; - GtkLabel *leaked_allocs; - GtkListBox *by_size; - GtkWidget *callgraph; - GtkWidget *summary_page; - GtkWidget *loading_state; - GtkWidget *empty_state; - - GCancellable *cancellable; - - GQueue *history; - - SysprofMemprofMode mode; - - guint profile_size; - guint loading; -} SysprofMemprofPagePrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofMemprofPage, sysprof_memprof_page, SYSPROF_TYPE_PAGE) - -enum { - PROP_0, - PROP_PROFILE, - N_PROPS -}; - -enum { - GO_PREVIOUS, - N_SIGNALS -}; - -enum { - COLUMN_NAME, - COLUMN_SELF, - COLUMN_TOTAL, - COLUMN_POINTER, - COLUMN_SIZE, -}; - -static void sysprof_memprof_page_update_descendants (SysprofMemprofPage *self, - StackNode *node); - -static GParamSpec *properties [N_PROPS]; -static guint signals [N_SIGNALS]; - -static guint -sysprof_memprof_page_get_profile_size (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - StackStash *stash; - StackNode *node; - guint size = 0; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - - if (priv->profile_size != 0) - return priv->profile_size; - - if (priv->profile == NULL) - return 0; - - if (NULL == (stash = sysprof_memprof_profile_get_stash (priv->profile))) - return 0; - - for (node = stack_stash_get_root (stash); node != NULL; node = node->siblings) - size += node->total; - - priv->profile_size = size; - - return size; -} - -static void -build_functions_store (StackNode *node, - gpointer user_data) -{ - struct { - GtkListStore *store; - gdouble profile_size; - } *state = user_data; - GtkTreeIter iter; - const StackNode *n; - guint64 size = 0; - guint64 total = 0; - - g_assert (state != NULL); - g_assert (GTK_IS_LIST_STORE (state->store)); - - for (n = node; n != NULL; n = n->next) - { - size += n->size; - if (n->toplevel) - total += n->total; - } - - gtk_list_store_append (state->store, &iter); - gtk_list_store_set (state->store, &iter, - COLUMN_NAME, U64_TO_POINTER (node->data), - COLUMN_SELF, 100.0 * size / state->profile_size, - COLUMN_TOTAL, 100.0 * total / state->profile_size, - COLUMN_POINTER, node, - COLUMN_SIZE, (guint64)total, - -1); - -} - -static void -update_summary (SysprofMemprofPage *self, - SysprofMemprofProfile *profile) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - SysprofMemprofStats stats; - g_autoptr(GString) str = NULL; - GtkWidget *child; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (SYSPROF_IS_MEMPROF_PROFILE (profile)); - - sysprof_memprof_profile_get_stats (profile, &stats); - - str = g_string_new (NULL); - - g_string_append_printf (str, "%"G_GINT64_FORMAT, stats.n_allocs); - gtk_label_set_label (priv->num_allocs, str->str); - g_string_truncate (str, 0); - - g_string_append_printf (str, "%"G_GINT64_FORMAT, stats.leaked_allocs); - gtk_label_set_label (priv->leaked_allocs, str->str); - g_string_truncate (str, 0); - - g_string_append_printf (str, "%"G_GINT64_FORMAT, stats.temp_allocs); - gtk_label_set_label (priv->temp_allocs_count, str->str); - g_string_truncate (str, 0); - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (priv->by_size)))) - gtk_list_box_remove (priv->by_size, child); - - for (guint i = 0; i < G_N_ELEMENTS (stats.by_size); i++) - { - g_autofree gchar *prevstr = NULL; - g_autofree gchar *sizestr = NULL; - g_autofree gchar *title_str = NULL; - g_autofree gchar *subtitle_str = NULL; - g_autofree gchar *allocstr = NULL; - g_autofree gchar *tempstr = NULL; - g_autofree gchar *allstr = NULL; - GtkWidget *row; - GtkWidget *title; - GtkWidget *subtitle; - GtkWidget *prog; - GtkWidget *box; - - if (stats.by_size[i].n_allocs == 0) - continue; - - row = gtk_list_box_row_new (); - title = gtk_label_new (NULL); - subtitle = gtk_label_new (NULL); - prog = gtk_level_bar_new_for_interval (0, stats.n_allocs); - box = g_object_new (GTK_TYPE_BOX, - "orientation", GTK_ORIENTATION_VERTICAL, - "spacing", 6, - "margin-top", 6, - "margin-start", 6, - "margin-bottom", 6, - "margin-end", 6, - NULL); - - sizestr = g_format_size_full (stats.by_size[i].bucket, G_FORMAT_SIZE_IEC_UNITS); - if (i == 0) - { - title_str = g_strdup_printf ("≤ %s", sizestr); - } - else - { - /* translators: %s is replaced with a memory size such as "32 bytes" */ - prevstr = g_format_size_full (stats.by_size[i-1].bucket, G_FORMAT_SIZE_IEC_UNITS); - /* translators: %s is replaced with the the lower and upper bound memory sizes in bytes */ - title_str = g_strdup_printf (_("> %s to %s"), prevstr, sizestr); - } - - gtk_label_set_label (GTK_LABEL (title), title_str); - gtk_label_set_xalign (GTK_LABEL (title), 0); - gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (title)), "dim-label"); - - gtk_widget_set_margin_start (box, 6); - gtk_widget_set_margin_end (box, 6); - - gtk_widget_set_margin_top (prog, 1); - gtk_widget_set_margin_bottom (prog, 1); - - allocstr = g_strdup_printf ("%"G_GINT64_FORMAT, stats.by_size[i].n_allocs); - tempstr = g_strdup_printf ("%"G_GINT64_FORMAT, stats.by_size[i].temp_allocs); - allstr = g_format_size_full (stats.by_size[i].allocated, - G_FORMAT_SIZE_IEC_UNITS); - subtitle_str = g_strdup_printf ("%s allocations, %s temporary, %s", - allocstr, tempstr, allstr); - - gtk_label_set_label (GTK_LABEL (subtitle), subtitle_str); - gtk_label_set_xalign (GTK_LABEL (subtitle), 0); - -#if 0 - /* TODO: Make this chunked by [temp][rest]... */ - gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (prog), - GTK_LEVEL_BAR_OFFSET_HIGH, - stats.by_size[i].temp_allocs); - gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (prog), - GTK_LEVEL_BAR_OFFSET_LOW, - stats.by_size[i].n_allocs); -#endif - gtk_level_bar_set_value (GTK_LEVEL_BAR (prog), - stats.by_size[i].n_allocs); - - gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), box); - gtk_box_append (GTK_BOX (box), title); - gtk_box_append (GTK_BOX (box), prog); - gtk_box_append (GTK_BOX (box), subtitle); - gtk_list_box_append (priv->by_size, row); - } -} - -static void -sysprof_memprof_page_load (SysprofMemprofPage *self, - SysprofMemprofProfile *profile) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkListStore *functions; - StackStash *stash; - StackNode *n; - GtkTreeIter iter; - struct { - GtkListStore *store; - gdouble profile_size; - } state = { 0 }; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (SYSPROF_IS_MEMPROF_PROFILE (profile)); - - /* - * TODO: This is probably the type of thing we want to do off the main - * thread. We should be able to build the tree models off thread - * and then simply apply them on the main thread. - * - * In the mean time, we should set the state of the widget to - * insensitive and give some indication of loading progress. - */ - - if (!g_set_object (&priv->profile, profile)) - return; - - update_summary (self, profile); - - if (sysprof_memprof_profile_is_empty (profile)) - { - gtk_stack_set_visible_child (priv->stack, priv->summary_page); - return; - } - - stash = sysprof_memprof_profile_get_stash (profile); - - for (n = stack_stash_get_root (stash); n; n = n->siblings) - state.profile_size += n->total; - - functions = gtk_list_store_new (5, - G_TYPE_STRING, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_POINTER, - G_TYPE_UINT64); - - state.store = functions; - stack_stash_foreach_by_address (stash, build_functions_store, &state); - - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (functions), - COLUMN_TOTAL, - GTK_SORT_DESCENDING); - - gtk_tree_view_set_model (priv->functions_view, GTK_TREE_MODEL (functions)); - gtk_tree_view_set_model (priv->callers_view, NULL); - gtk_tree_view_set_model (priv->descendants_view, NULL); - - if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (functions), &iter)) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (priv->functions_view); - gtk_tree_selection_select_iter (selection, &iter); - } - - gtk_stack_set_visible_child (priv->stack, priv->callgraph); - - g_clear_object (&functions); -} - -void -_sysprof_memprof_page_set_failed (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_MEMPROF_PAGE (self)); - - gtk_stack_set_visible_child (priv->stack, priv->empty_state); -} - -static void -sysprof_memprof_page_unload (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (SYSPROF_IS_MEMPROF_PROFILE (priv->profile)); - - g_queue_clear (priv->history); - g_clear_object (&priv->profile); - priv->profile_size = 0; - - gtk_tree_view_set_model (priv->callers_view, NULL); - gtk_tree_view_set_model (priv->functions_view, NULL); - gtk_tree_view_set_model (priv->descendants_view, NULL); - - gtk_stack_set_visible_child (priv->stack, priv->empty_state); -} - -/** - * sysprof_memprof_page_get_profile: - * - * Returns: (transfer none): An #SysprofMemprofProfile. - */ -SysprofMemprofProfile * -sysprof_memprof_page_get_profile (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PAGE (self), NULL); - - return priv->profile; -} - -void -sysprof_memprof_page_set_profile (SysprofMemprofPage *self, - SysprofMemprofProfile *profile) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_MEMPROF_PAGE (self)); - g_return_if_fail (!profile || SYSPROF_IS_MEMPROF_PROFILE (profile)); - - if (profile != priv->profile) - { - if (priv->profile) - sysprof_memprof_page_unload (self); - - if (profile) - sysprof_memprof_page_load (self, profile); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROFILE]); - } -} - -static void -sysprof_memprof_page_expand_descendants (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeModel *model; - GList *all_paths = NULL; - GtkTreePath *first_path; - GtkTreeIter iter; - gdouble top_value = 0; - gint max_rows = 40; /* FIXME */ - gint n_rows; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - - model = gtk_tree_view_get_model (priv->descendants_view); - first_path = gtk_tree_path_new_first (); - all_paths = g_list_prepend (all_paths, first_path); - n_rows = 1; - - gtk_tree_model_get_iter (model, &iter, first_path); - gtk_tree_model_get (model, &iter, - COLUMN_TOTAL, &top_value, - -1); - - while ((all_paths != NULL) && (n_rows < max_rows)) - { - GtkTreeIter best_iter; - GtkTreePath *best_path = NULL; - GList *list; - gdouble best_value = 0.0; - gint n_children; - gint i; - - for (list = all_paths; list != NULL; list = list->next) - { - GtkTreePath *path = list->data; - - g_assert (path != NULL); - - if (gtk_tree_model_get_iter (model, &iter, path)) - { - gdouble value; - - gtk_tree_model_get (model, &iter, - COLUMN_TOTAL, &value, - -1); - - if (value >= best_value) - { - best_value = value; - best_path = path; - best_iter = iter; - } - } - } - - n_children = gtk_tree_model_iter_n_children (model, &best_iter); - - if ((n_children > 0) && - ((best_value / top_value) > 0.04) && - ((n_children + gtk_tree_path_get_depth (best_path)) / (gdouble)max_rows) < (best_value / top_value)) - { - gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE); - n_rows += n_children; - - if (gtk_tree_path_get_depth (best_path) < 4) - { - GtkTreePath *path; - - path = gtk_tree_path_copy (best_path); - gtk_tree_path_down (path); - - for (i = 0; i < n_children; i++) - { - all_paths = g_list_prepend (all_paths, path); - - path = gtk_tree_path_copy (path); - gtk_tree_path_next (path); - } - - gtk_tree_path_free (path); - } - } - - all_paths = g_list_remove (all_paths, best_path); - - /* Always expand at least once */ - if ((all_paths == NULL) && (n_rows == 1)) - gtk_tree_view_expand_row (priv->descendants_view, best_path, FALSE); - - gtk_tree_path_free (best_path); - } - - g_list_free_full (all_paths, (GDestroyNotify)gtk_tree_path_free); -} - -typedef struct -{ - StackNode *node; - const gchar *name; - guint self; - guint total; -} Caller; - -static Caller * -caller_new (StackNode *node) -{ - Caller *c; - - c = g_slice_new (Caller); - c->name = U64_TO_POINTER (node->data); - c->self = 0; - c->total = 0; - c->node = node; - - return c; -} - -static void -caller_free (gpointer data) -{ - Caller *c = data; - g_slice_free (Caller, c); -} - -static void -sysprof_memprof_page_function_selection_changed (SysprofMemprofPage *self, - GtkTreeSelection *selection) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeModel *model = NULL; - GtkTreeIter iter; - GtkListStore *callers_store; - g_autoptr(GHashTable) callers = NULL; - g_autoptr(GHashTable) processed = NULL; - StackNode *callees = NULL; - StackNode *node; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (GTK_IS_TREE_SELECTION (selection)); - - if (!gtk_tree_selection_get_selected (selection, &model, &iter)) - { - gtk_tree_view_set_model (priv->callers_view, NULL); - gtk_tree_view_set_model (priv->descendants_view, NULL); - return; - } - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &callees, - -1); - - sysprof_memprof_page_update_descendants (self, callees); - - callers_store = gtk_list_store_new (5, - G_TYPE_STRING, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_POINTER, - G_TYPE_UINT64); - - callers = g_hash_table_new_full (NULL, NULL, NULL, caller_free); - processed = g_hash_table_new (NULL, NULL); - - for (node = callees; node != NULL; node = node->next) - { - Caller *c; - - if (!node->parent) - continue; - - c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data)); - - if (c == NULL) - { - c = caller_new (node->parent); - g_hash_table_insert (callers, (gpointer)c->name, c); - } - } - - for (node = callees; node != NULL; node = node->next) - { - StackNode *top_caller = node->parent; - StackNode *top_callee = node; - StackNode *n; - Caller *c; - - if (!node->parent) - continue; - - /* - * We could have a situation where the function was called in a - * reentrant fashion, so we want to take the top-most match in the - * stack. - */ - for (n = node; n && n->parent; n = n->parent) - { - if (n->data == node->data && n->parent->data == node->parent->data) - { - top_caller = n->parent; - top_callee = n; - } - } - - c = g_hash_table_lookup (callers, U64_TO_POINTER (node->parent->data)); - - g_assert (c != NULL); - - if (!g_hash_table_lookup (processed, top_caller)) - { - c->total += top_callee->total; - g_hash_table_insert (processed, top_caller, top_caller); - } - - c->self += node->size; - } - - { - GHashTableIter hiter; - gpointer key, value; - guint64 size = 0; - - size = MAX (1, sysprof_memprof_page_get_profile_size (self)); - - g_hash_table_iter_init (&hiter, callers); - - while (g_hash_table_iter_next (&hiter, &key, &value)) - { - Caller *c = value; - - gtk_list_store_append (callers_store, &iter); - gtk_list_store_set (callers_store, &iter, - COLUMN_NAME, c->name, - COLUMN_SELF, c->self * 100.0 / size, - COLUMN_TOTAL, c->total * 100.0 / size, - COLUMN_POINTER, c->node, - COLUMN_SIZE, (guint64)c->total, - -1); - } - } - - gtk_tree_view_set_model (priv->callers_view, GTK_TREE_MODEL (callers_store)); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (callers_store), - COLUMN_TOTAL, - GTK_SORT_DESCENDING); - - g_clear_object (&callers_store); -} - -static void -sysprof_memprof_page_set_node (SysprofMemprofPage *self, - StackNode *node) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeModel *model; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (node != NULL); - - if (priv->profile == NULL) - return; - - model = gtk_tree_view_get_model (priv->functions_view); - - if (gtk_tree_model_get_iter_first (model, &iter)) - { - do - { - StackNode *item = NULL; - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &item, - -1); - - if (item != NULL && item->data == node->data) - { - GtkTreeSelection *selection; - - selection = gtk_tree_view_get_selection (priv->functions_view); - gtk_tree_selection_select_iter (selection, &iter); - - break; - } - } - while (gtk_tree_model_iter_next (model, &iter)); - } -} - -static void -sysprof_memprof_page_descendant_activated (SysprofMemprofPage *self, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkTreeView *tree_view) -{ - GtkTreeModel *model; - StackNode *node = NULL; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (GTK_IS_TREE_VIEW (tree_view)); - g_assert (path != NULL); - g_assert (GTK_IS_TREE_VIEW_COLUMN (column)); - - model = gtk_tree_view_get_model (tree_view); - - if (!gtk_tree_model_get_iter (model, &iter, path)) - return; - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &node, - -1); - - if (node != NULL) - sysprof_memprof_page_set_node (self, node); -} - -static void -sysprof_memprof_page_caller_activated (SysprofMemprofPage *self, - GtkTreePath *path, - GtkTreeViewColumn *column, - GtkTreeView *tree_view) -{ - GtkTreeModel *model; - StackNode *node = NULL; - GtkTreeIter iter; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (GTK_IS_TREE_VIEW (tree_view)); - g_assert (path != NULL); - g_assert (GTK_IS_TREE_VIEW_COLUMN (column)); - - model = gtk_tree_view_get_model (tree_view); - - if (!gtk_tree_model_get_iter (model, &iter, path)) - return; - - gtk_tree_model_get (model, &iter, - COLUMN_POINTER, &node, - -1); - - if (node != NULL) - sysprof_memprof_page_set_node (self, node); -} - -static void -sysprof_memprof_page_size_data_func (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - g_autofree gchar *size_str = NULL; - guint64 size; - - gtk_tree_model_get (model, iter, COLUMN_SIZE, &size, -1); - if (size) - size_str = g_format_size_full (size, G_FORMAT_SIZE_IEC_UNITS); - g_object_set (cell, "text", size_str, NULL); -} - -static void -sysprof_memprof_page_tag_data_func (GtkTreeViewColumn *column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data) -{ - SysprofMemprofPage *self = data; - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - StackNode *node = NULL; - const gchar *str = NULL; - - if (priv->profile == NULL) - return; - - gtk_tree_model_get (model, iter, COLUMN_POINTER, &node, -1); - - if (node && node->data) - { - GQuark tag; - - tag = sysprof_memprof_profile_get_tag (priv->profile, GSIZE_TO_POINTER (node->data)); - if (tag != 0) - str = g_quark_to_string (tag); - } - - g_object_set (cell, "text", str, NULL); -} - -static void -sysprof_memprof_page_real_go_previous (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - StackNode *node; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - - node = g_queue_pop_head (priv->history); - - if (NULL != (node = g_queue_peek_head (priv->history))) - sysprof_memprof_page_set_node (self, node); -} - -static void -descendants_view_move_cursor_cb (GtkTreeView *descendants_view, - GtkMovementStep step, - int direction, - gpointer user_data) -{ - if (step == GTK_MOVEMENT_VISUAL_POSITIONS) - { - GtkTreePath *path; - - gtk_tree_view_get_cursor (descendants_view, &path, NULL); - - if (direction == 1) - { - gtk_tree_view_expand_row (descendants_view, path, FALSE); - g_signal_stop_emission_by_name (descendants_view, "move-cursor"); - } - else if (direction == -1) - { - gtk_tree_view_collapse_row (descendants_view, path); - g_signal_stop_emission_by_name (descendants_view, "move-cursor"); - } - - gtk_tree_path_free (path); - } -} - -static void -copy_tree_view_selection_cb (GtkTreeModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer data) -{ - g_autofree gchar *name = NULL; - g_autofree gchar *size_str = NULL; - gchar tstr[16]; - GString *str = data; - gdouble total; - guint64 size; - gint depth; - - g_assert (GTK_IS_TREE_MODEL (model)); - g_assert (path != NULL); - g_assert (iter != NULL); - g_assert (str != NULL); - - depth = gtk_tree_path_get_depth (path); - gtk_tree_model_get (model, iter, - COLUMN_NAME, &name, - COLUMN_TOTAL, &total, - COLUMN_SIZE, &size, - -1); - - size_str = g_format_size_full (size, G_FORMAT_SIZE_IEC_UNITS); - g_snprintf (tstr, sizeof tstr, "%.2lf%%", total); - - g_string_append_printf (str, "[%12s] [%8s] ", size_str, tstr); - - for (gint i = 1; i < depth; i++) - g_string_append (str, " "); - g_string_append (str, name); - g_string_append_c (str, '\n'); -} - -static void -copy_tree_view_selection (GtkTreeView *tree_view) -{ - g_autoptr(GString) str = NULL; - GdkClipboard *clipboard; - - g_assert (GTK_IS_TREE_VIEW (tree_view)); - - str = g_string_new (" ALLOCATED TOTAL FUNCTION\n"); - gtk_tree_selection_selected_foreach (gtk_tree_view_get_selection (tree_view), - copy_tree_view_selection_cb, - str); - - clipboard = gtk_widget_get_clipboard (GTK_WIDGET (tree_view)); - gdk_clipboard_set_text (clipboard, str->str); -} - -static void -sysprof_memprof_page_copy_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) -{ - SysprofMemprofPage *self = (SysprofMemprofPage *)widget; - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkWidget *focus; - GtkRoot *toplevel; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - - if (!(toplevel = gtk_widget_get_root (widget)) || - !GTK_IS_ROOT (toplevel) || - !(focus = gtk_root_get_focus (toplevel))) - return; - - if (focus == GTK_WIDGET (priv->descendants_view)) - copy_tree_view_selection (priv->descendants_view); - else if (focus == GTK_WIDGET (priv->callers_view)) - copy_tree_view_selection (priv->callers_view); - else if (focus == GTK_WIDGET (priv->functions_view)) - copy_tree_view_selection (priv->functions_view); -} - -static void -sysprof_memprof_page_generate_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofProfile *profile = (SysprofProfile *)object; - SysprofMemprofPage *self; - g_autoptr(GTask) task = user_data; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_PROFILE (profile)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - self = g_task_get_source_object (task); - - if (!sysprof_profile_generate_finish (profile, result, &error)) - g_task_return_error (task, g_error_copy (error)); - else - sysprof_memprof_page_set_profile (self, SYSPROF_MEMPROF_PROFILE (profile)); -} - -static void -sysprof_memprof_page_load_async (SysprofPage *page, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *filter, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofMemprofPage *self = (SysprofMemprofPage *)page; - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - g_autoptr(SysprofCaptureReader) copy = NULL; - g_autoptr(SysprofProfile) profile = NULL; - g_autoptr(GTask) task = NULL; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_SELECTION (selection)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - g_cancellable_cancel (priv->cancellable); - - if (cancellable == NULL) - cancellable = priv->cancellable = g_cancellable_new (); - else - g_set_object (&priv->cancellable, cancellable); - - gtk_stack_set_visible_child (priv->stack, priv->loading_state); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_memprof_page_load_async); - - copy = sysprof_capture_reader_copy (reader); - - profile = sysprof_memprof_profile_new_with_selection (selection); - sysprof_memprof_profile_set_mode (SYSPROF_MEMPROF_PROFILE (profile), priv->mode); - sysprof_profile_set_reader (profile, reader); - sysprof_profile_generate (profile, - cancellable, - sysprof_memprof_page_generate_cb, - g_steal_pointer (&task)); -} - -static gboolean -sysprof_memprof_page_load_finish (SysprofPage *page, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PAGE (page), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -do_allocs (SysprofMemprofPage *self, - SysprofMemprofMode mode) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - - priv->mode = mode; - sysprof_page_reload (SYSPROF_PAGE (self)); -} - -static void -mode_notify_active (SysprofMemprofPage *self, - GParamSpec *pspec, - GtkToggleButton *button) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - g_assert (GTK_IS_TOGGLE_BUTTON (button)); - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) - { - if (button == priv->summary) - do_allocs (self, SYSPROF_MEMPROF_MODE_SUMMARY); - else if (button == priv->all_allocs) - do_allocs (self, SYSPROF_MEMPROF_MODE_ALL_ALLOCS); - else if (button == priv->temp_allocs) - do_allocs (self, SYSPROF_MEMPROF_MODE_TEMP_ALLOCS); - else if (button == priv->leaked_allocs_button) - do_allocs (self, SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS); - } -} - -static void -sep_header_func (GtkListBoxRow *row, - GtkListBoxRow *before, - gpointer user_data) -{ - if (before != NULL) - gtk_list_box_row_set_header (row, - g_object_new (GTK_TYPE_SEPARATOR, - "orientation", GTK_ORIENTATION_HORIZONTAL, - "visible", TRUE, - NULL)); -} - -static void -sysprof_memprof_page_finalize (GObject *object) -{ - SysprofMemprofPage *self = (SysprofMemprofPage *)object; - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_clear_pointer (&priv->history, g_queue_free); - g_clear_object (&priv->profile); - g_clear_object (&priv->cancellable); - - G_OBJECT_CLASS (sysprof_memprof_page_parent_class)->finalize (object); -} - -static void -sysprof_memprof_page_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofMemprofPage *self = SYSPROF_MEMPROF_PAGE (object); - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - switch (prop_id) - { - case PROP_PROFILE: - g_value_set_object (value, priv->profile); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_memprof_page_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofMemprofPage *self = SYSPROF_MEMPROF_PAGE (object); - - switch (prop_id) - { - case PROP_PROFILE: - sysprof_memprof_page_set_profile (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_memprof_page_class_init (SysprofMemprofPageClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofPageClass *page_class = SYSPROF_PAGE_CLASS (klass); - - object_class->finalize = sysprof_memprof_page_finalize; - object_class->get_property = sysprof_memprof_page_get_property; - object_class->set_property = sysprof_memprof_page_set_property; - - page_class->load_async = sysprof_memprof_page_load_async; - page_class->load_finish = sysprof_memprof_page_load_finish; - - klass->go_previous = sysprof_memprof_page_real_go_previous; - - properties [PROP_PROFILE] = - g_param_spec_object ("profile", - "Profile", - "The callgraph profile to view", - SYSPROF_TYPE_MEMPROF_PROFILE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - signals [GO_PREVIOUS] = - g_signal_new ("go-previous", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (SysprofMemprofPageClass, go_previous), - NULL, NULL, NULL, G_TYPE_NONE, 0); - - gtk_widget_class_set_template_from_resource (widget_class, - "/org/gnome/sysprof/ui/sysprof-memprof-page.ui"); - - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, by_size); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, callers_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, function_size_cell); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, function_size_column); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, functions_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, descendants_view); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, descendants_name_column); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, stack); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, all_allocs); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, temp_allocs); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, summary); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, temp_allocs_count); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, num_allocs); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, leaked_allocs); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, leaked_allocs_button); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, loading_state); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, empty_state); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, summary_page); - gtk_widget_class_bind_template_child_private (widget_class, SysprofMemprofPage, callgraph); - - gtk_widget_class_install_action (widget_class, "page.copy", NULL, sysprof_memprof_page_copy_cb); - - gtk_widget_class_add_binding_action (widget_class, GDK_KEY_c, GDK_CONTROL_MASK, "page.copy", NULL); - gtk_widget_class_add_binding_signal (widget_class, GDK_KEY_Left, GDK_ALT_MASK, "go-previous", NULL); - - g_type_ensure (EGG_TYPE_PANED); - g_type_ensure (SYSPROF_TYPE_CELL_RENDERER_PERCENT); -} - -static void -sysprof_memprof_page_init (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeSelection *selection; - GtkCellRenderer *cell; - - priv->history = g_queue_new (); - priv->mode = SYSPROF_MEMPROF_MODE_ALL_ALLOCS; - - gtk_widget_init_template (GTK_WIDGET (self)); - - gtk_stack_set_visible_child (priv->stack, priv->empty_state); - - gtk_list_box_set_header_func (priv->by_size, sep_header_func, NULL, NULL); - - g_signal_connect_object (priv->all_allocs, - "notify::active", - G_CALLBACK (mode_notify_active), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (priv->temp_allocs, - "notify::active", - G_CALLBACK (mode_notify_active), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (priv->leaked_allocs_button, - "notify::active", - G_CALLBACK (mode_notify_active), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (priv->summary, - "notify::active", - G_CALLBACK (mode_notify_active), - self, - G_CONNECT_SWAPPED); - - selection = gtk_tree_view_get_selection (priv->functions_view); - - g_signal_connect_object (selection, - "changed", - G_CALLBACK (sysprof_memprof_page_function_selection_changed), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->descendants_view, - "row-activated", - G_CALLBACK (sysprof_memprof_page_descendant_activated), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->callers_view, - "row-activated", - G_CALLBACK (sysprof_memprof_page_caller_activated), - self, - G_CONNECT_SWAPPED); - - g_signal_connect (priv->descendants_view, - "move-cursor", - G_CALLBACK (descendants_view_move_cursor_cb), - NULL); - - cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, - "ellipsize", PANGO_ELLIPSIZE_MIDDLE, - "xalign", 0.0f, - NULL); - gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, TRUE); - gtk_tree_view_column_add_attribute (priv->descendants_name_column, cell, "text", 0); - - cell = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, - "foreground", "#666666", - "scale", PANGO_SCALE_SMALL, - "xalign", 1.0f, - NULL); - gtk_tree_view_column_pack_start (priv->descendants_name_column, cell, FALSE); - gtk_tree_view_column_set_cell_data_func (priv->descendants_name_column, cell, - sysprof_memprof_page_tag_data_func, - self, NULL); - - gtk_tree_view_column_set_cell_data_func (priv->function_size_column, - GTK_CELL_RENDERER (priv->function_size_cell), - sysprof_memprof_page_size_data_func, - self, NULL); - - gtk_tree_selection_set_mode (gtk_tree_view_get_selection (priv->descendants_view), - GTK_SELECTION_MULTIPLE); -} - -typedef struct _Descendant Descendant; - -struct _Descendant -{ - const gchar *name; - guint self; - guint cumulative; - Descendant *parent; - Descendant *siblings; - Descendant *children; -}; - -static void -build_tree_cb (StackLink *trace, - gint size, - gpointer user_data) -{ - Descendant **tree = user_data; - Descendant *parent = NULL; - StackLink *link; - - g_assert (trace != NULL); - g_assert (tree != NULL); - - /* Get last item */ - link = trace; - while (link->next) - link = link->next; - - for (; link != NULL; link = link->prev) - { - const gchar *address = U64_TO_POINTER (link->data); - Descendant *prev = NULL; - Descendant *match = NULL; - - for (match = *tree; match != NULL; match = match->siblings) - { - if (match->name == address) - { - if (prev != NULL) - { - /* Move to front */ - prev->siblings = match->siblings; - match->siblings = *tree; - *tree = match; - } - break; - } - } - - if (match == NULL) - { - /* Have we seen this object further up the tree? */ - for (match = parent; match != NULL; match = match->parent) - { - if (match->name == address) - break; - } - } - - if (match == NULL) - { - match = g_slice_new (Descendant); - match->name = address; - match->cumulative = 0; - match->self = 0; - match->children = NULL; - match->parent = parent; - match->siblings = *tree; - *tree = match; - } - - tree = &match->children; - parent = match; - } - - parent->self += size; - - for (; parent != NULL; parent = parent->parent) - parent->cumulative += size; -} - -static Descendant * -build_tree (StackNode *node) -{ - Descendant *tree = NULL; - - for (; node != NULL; node = node->next) - { - if (node->toplevel) - stack_node_foreach_trace (node, build_tree_cb, &tree); - } - - return tree; -} - -static void -append_to_tree_and_free (SysprofMemprofPage *self, - StackStash *stash, - GtkTreeStore *store, - Descendant *item, - GtkTreeIter *parent) -{ - StackNode *node = NULL; - GtkTreeIter iter; - guint profile_size; - - g_assert (GTK_IS_TREE_STORE (store)); - g_assert (item != NULL); - - profile_size = MAX (1, sysprof_memprof_page_get_profile_size (self)); - - gtk_tree_store_append (store, &iter, parent); - - node = stack_stash_find_node (stash, (gpointer)item->name); - - gtk_tree_store_set (store, &iter, - COLUMN_NAME, item->name, - COLUMN_SELF, item->self * 100.0 / (gdouble)profile_size, - COLUMN_TOTAL, item->cumulative * 100.0 / (gdouble)profile_size, - COLUMN_POINTER, node, - COLUMN_SIZE, item->cumulative, - -1); - - if (item->siblings != NULL) - append_to_tree_and_free (self, stash, store, item->siblings, parent); - - if (item->children != NULL) - append_to_tree_and_free (self, stash, store, item->children, &iter); - - g_slice_free (Descendant, item); -} - -static void -sysprof_memprof_page_update_descendants (SysprofMemprofPage *self, - StackNode *node) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeStore *store; - - g_assert (SYSPROF_IS_MEMPROF_PAGE (self)); - - if (g_queue_peek_head (priv->history) != node) - g_queue_push_head (priv->history, node); - - store = gtk_tree_store_new (5, - G_TYPE_STRING, - G_TYPE_DOUBLE, - G_TYPE_DOUBLE, - G_TYPE_POINTER, - G_TYPE_UINT64); - - if (priv->profile != NULL) - { - StackStash *stash; - - stash = sysprof_memprof_profile_get_stash (priv->profile); - if (stash != NULL) - { - Descendant *tree; - - tree = build_tree (node); - if (tree != NULL) - append_to_tree_and_free (self, stash, store, tree, NULL); - } - } - - gtk_tree_view_set_model (priv->descendants_view, GTK_TREE_MODEL (store)); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), - COLUMN_TOTAL, GTK_SORT_DESCENDING); - sysprof_memprof_page_expand_descendants (self); - - g_clear_object (&store); -} - -/** - * sysprof_memprof_page_screenshot: - * @self: A #SysprofMemprofPage. - * - * This function will generate a text representation of the descendants tree. - * This is useful if you want to include various profiling information in a - * commit message or email. - * - * The text generated will match the current row expansion in the tree view. - * - * Returns: (nullable) (transfer full): A newly allocated string that should be freed - * with g_free(). - */ -gchar * -sysprof_memprof_page_screenshot (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeView *tree_view; - GtkTreeModel *model; - GtkTreePath *tree_path; - GString *str; - GtkTreeIter iter; - - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PAGE (self), NULL); - - tree_view = priv->descendants_view; - - if (NULL == (model = gtk_tree_view_get_model (tree_view))) - return NULL; - - /* - * To avoid having to precalculate the deepest visible row, we - * put the timing information at the beginning of the line. - */ - - str = g_string_new (" SELF CUMULATIVE FUNCTION\n"); - tree_path = gtk_tree_path_new_first (); - - for (;;) - { - if (gtk_tree_model_get_iter (model, &iter, tree_path)) - { - guint depth = gtk_tree_path_get_depth (tree_path); - StackNode *node; - gdouble in_self; - gdouble total; - guint i; - - gtk_tree_model_get (model, &iter, - COLUMN_SELF, &in_self, - COLUMN_TOTAL, &total, - COLUMN_POINTER, &node, - -1); - - g_string_append_printf (str, "[% 7.2lf%%] [% 7.2lf%%] ", in_self, total); - - for (i = 0; i < depth; i++) - g_string_append (str, " "); - g_string_append (str, GSIZE_TO_POINTER (node->data)); - g_string_append_c (str, '\n'); - - if (gtk_tree_view_row_expanded (tree_view, tree_path)) - gtk_tree_path_down (tree_path); - else - gtk_tree_path_next (tree_path); - - continue; - } - - if (!gtk_tree_path_up (tree_path) || !gtk_tree_path_get_depth (tree_path)) - break; - - gtk_tree_path_next (tree_path); - } - - gtk_tree_path_free (tree_path); - - return g_string_free (str, FALSE); -} - -guint -sysprof_memprof_page_get_n_functions (SysprofMemprofPage *self) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - GtkTreeModel *model; - guint ret = 0; - - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PAGE (self), 0); - - if (NULL != (model = gtk_tree_view_get_model (priv->functions_view))) - ret = gtk_tree_model_iter_n_children (model, NULL); - - return ret; -} - -void -_sysprof_memprof_page_set_loading (SysprofMemprofPage *self, - gboolean loading) -{ - SysprofMemprofPagePrivate *priv = sysprof_memprof_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_MEMPROF_PAGE (self)); - - if (loading) - priv->loading++; - else - priv->loading--; - - if (priv->loading) - gtk_stack_set_visible_child (priv->stack, priv->loading_state); - else - gtk_stack_set_visible_child (priv->stack, priv->callgraph); -} diff --git a/src/libsysprof-ui/sysprof-memprof-page.h b/src/libsysprof-ui/sysprof-memprof-page.h deleted file mode 100644 index 1b3f7af3..00000000 --- a/src/libsysprof-ui/sysprof-memprof-page.h +++ /dev/null @@ -1,51 +0,0 @@ -/* sysprof-memprof-page.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -#include "sysprof-page.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MEMPROF_PAGE (sysprof_memprof_page_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofMemprofPage, sysprof_memprof_page, SYSPROF, MEMPROF_PAGE, SysprofPage) - -struct _SysprofMemprofPageClass -{ - SysprofPageClass parent_class; - - void (*go_previous) (SysprofMemprofPage *self); - - /*< private >*/ - gpointer _reserved[16]; -}; - -GtkWidget *sysprof_memprof_page_new (void); -SysprofMemprofProfile *sysprof_memprof_page_get_profile (SysprofMemprofPage *self); -void sysprof_memprof_page_set_profile (SysprofMemprofPage *self, - SysprofMemprofProfile *profile); -gchar *sysprof_memprof_page_screenshot (SysprofMemprofPage *self); -guint sysprof_memprof_page_get_n_functions (SysprofMemprofPage *self); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-memprof-page.ui b/src/libsysprof-ui/sysprof-memprof-page.ui deleted file mode 100644 index ca22240a..00000000 --- a/src/libsysprof-ui/sysprof-memprof-page.ui +++ /dev/null @@ -1,330 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-memprof-visualizer.c b/src/libsysprof-ui/sysprof-memprof-visualizer.c deleted file mode 100644 index a117dd7f..00000000 --- a/src/libsysprof-ui/sysprof-memprof-visualizer.c +++ /dev/null @@ -1,613 +0,0 @@ -/* sysprof-memprof-visualizer.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#define G_LOG_DOMAIN "sysprof-memprof-visualizer" - -#include -#include -#include - -#include "rax.h" - -#include "sysprof-memprof-visualizer.h" - -typedef struct -{ - cairo_surface_t *surface; - SysprofCaptureReader *reader; - rax *rax; - GtkAllocation alloc; - gint64 begin_time; - gint64 duration; - gint64 total_alloc; - gint64 max_alloc; - GdkRGBA fg; - GdkRGBA fg2; - guint scale; -} DrawContext; - -struct _SysprofMemprofVisualizer -{ - SysprofVisualizer parent_instance; - - SysprofCaptureReader *reader; - GCancellable *cancellable; - - cairo_surface_t *surface; - gint surface_w; - gint surface_h; - - guint queued_draw; - - gint64 begin_time; - gint64 duration; - - gint64 cached_total_alloc; - gint64 cached_max_alloc; - - guint mode : 1; -}; - -enum { - MODE_ALLOCS, - MODE_TOTAL, -}; - -G_DEFINE_TYPE (SysprofMemprofVisualizer, sysprof_memprof_visualizer, SYSPROF_TYPE_VISUALIZER) - -static void -draw_context_free (DrawContext *draw) -{ - g_clear_pointer (&draw->reader, sysprof_capture_reader_unref); - g_clear_pointer (&draw->surface, cairo_surface_destroy); - g_clear_pointer (&draw->rax, raxFree); - g_slice_free (DrawContext, draw); -} - -static void -sysprof_memprof_visualizer_set_reader (SysprofVisualizer *visualizer, - SysprofCaptureReader *reader) -{ - SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)visualizer; - - g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self)); - - if (reader == self->reader) - return; - - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - - self->reader = sysprof_capture_reader_ref (reader); - self->begin_time = sysprof_capture_reader_get_start_time (reader); - self->duration = sysprof_capture_reader_get_end_time (reader) - - sysprof_capture_reader_get_start_time (reader); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -SysprofVisualizer * -sysprof_memprof_visualizer_new (gboolean total_allocs) -{ - SysprofMemprofVisualizer *self; - - self = g_object_new (SYSPROF_TYPE_MEMPROF_VISUALIZER, - "title", total_allocs ? _("Memory Used") : _("Memory Allocations"), - "height-request", 35, - "visible", TRUE, - NULL); - - if (total_allocs) - self->mode = MODE_TOTAL; - else - self->mode = MODE_ALLOCS; - - return SYSPROF_VISUALIZER (self); -} - -static guint64 -get_max_alloc (SysprofCaptureReader *reader) -{ - SysprofCaptureFrameType type; - gint64 ret = 0; - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - const SysprofCaptureAllocation *ev; - - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - if (!(ev = sysprof_capture_reader_read_allocation (reader))) - break; - - if (ev->alloc_size > ret) - ret = ev->alloc_size; - } - else - { - if (!sysprof_capture_reader_skip (reader)) - break; - continue; - } - } - - sysprof_capture_reader_reset (reader); - - return ret; -} - -static guint64 -get_total_alloc (SysprofCaptureReader *reader) -{ - SysprofCaptureFrameType type; - guint64 total = 0; - guint64 max = 0; - rax *r; - - r = raxNew (); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - const SysprofCaptureAllocation *ev; - - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - if (!(ev = sysprof_capture_reader_read_allocation (reader))) - break; - - if (ev->alloc_size > 0) - { - raxInsert (r, - (guint8 *)&ev->alloc_addr, - sizeof ev->alloc_addr, - GSIZE_TO_POINTER (ev->alloc_size), - NULL); - - total += ev->alloc_size; - - if (total > max) - max = total; - } - else - { - gpointer res = raxFind (r, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr); - - if (res != raxNotFound) - { - total -= GPOINTER_TO_SIZE (res); - raxRemove (r, - (guint8 *)&ev->alloc_addr, - sizeof ev->alloc_addr, - NULL); - } - } - } - else - { - if (!sysprof_capture_reader_skip (reader)) - break; - continue; - } - } - - sysprof_capture_reader_reset (reader); - raxFree (r); - - return max; -} - -static void -draw_total_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofCaptureFrameType type; - DrawContext *draw = task_data; - gint64 total = 0; - cairo_t *cr; - rax *r; - gint x = 0; - - g_assert (G_IS_TASK (task)); - g_assert (draw != NULL); - g_assert (draw->surface != NULL); - g_assert (draw->reader != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - if (draw->total_alloc == 0) - draw->total_alloc = get_total_alloc (draw->reader); - - r = raxNew (); - - /* To avoid sorting, this code assums that all allocation information - * is sorted and in order. Generally this is the case, but a crafted - * syscap file could break it on purpose if they tried. - */ - - cr = cairo_create (draw->surface); - cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); - cairo_set_source_rgb (cr, 0, 0, 0); - - while (sysprof_capture_reader_peek_type (draw->reader, &type)) - { - const SysprofCaptureAllocation *ev; - gint y; - - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - if (!(ev = sysprof_capture_reader_read_allocation (draw->reader))) - break; - - if (ev->alloc_size > 0) - { - raxInsert (r, - (guint8 *)&ev->alloc_addr, - sizeof ev->alloc_addr, - GSIZE_TO_POINTER (ev->alloc_size), - NULL); - - total += ev->alloc_size; - } - else - { - gpointer res = raxFind (r, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr); - - if (res != raxNotFound) - { - total -= GPOINTER_TO_SIZE (res); - raxRemove (r, - (guint8 *)&ev->alloc_addr, - sizeof ev->alloc_addr, - NULL); - } - } - } - else - { - if (!sysprof_capture_reader_skip (draw->reader)) - break; - continue; - } - - x = (ev->frame.time - draw->begin_time) / (gdouble)draw->duration * draw->alloc.width; - y = draw->alloc.height - ((gdouble)total / (gdouble)draw->total_alloc * (gdouble)draw->alloc.height); - - cairo_rectangle (cr, x, y, 1, 1); - cairo_fill (cr); - } - - cairo_destroy (cr); - - g_task_return_boolean (task, TRUE); - - raxFree (r); -} - -static void -draw_alloc_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - static const gdouble dashes[] = { 1.0, 2.0 }; - DrawContext *draw = task_data; - SysprofCaptureFrameType type; - GdkRGBA *last; - GdkRGBA mid; - cairo_t *cr; - guint counter = 0; - gint midpt; - gdouble log_max; - - g_assert (G_IS_TASK (task)); - g_assert (draw != NULL); - g_assert (draw->surface != NULL); - g_assert (draw->reader != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - if (draw->max_alloc == 0) - draw->max_alloc = get_max_alloc (draw->reader); - - log_max = log10 (draw->max_alloc); - midpt = draw->alloc.height / 2; - - cr = cairo_create (draw->surface); - - /* Draw mid-point line */ - mid = draw->fg; - mid.alpha *= 0.4; - cairo_set_line_width (cr, 1.0); - gdk_cairo_set_source_rgba (cr, &mid); - cairo_move_to (cr, 0, midpt); - cairo_line_to (cr, draw->alloc.width, midpt); - cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0); - cairo_stroke (cr); - - cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); - gdk_cairo_set_source_rgba (cr, &draw->fg); - last = &draw->fg; - - /* Now draw data points */ - while (sysprof_capture_reader_peek_type (draw->reader, &type)) - { - const SysprofCaptureAllocation *ev; - gint64 size; - gdouble l; - gint x; - gint y; - - /* Cancellation check every 1000 frames */ - if G_UNLIKELY (++counter == 1000) - { - if (g_task_return_error_if_cancelled (task)) - { - cairo_destroy (cr); - return; - } - - counter = 0; - } - - /* We only care about memory frames here */ - if (type != SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - if (!sysprof_capture_reader_skip (draw->reader)) - break; - continue; - } - - if (!(ev = sysprof_capture_reader_read_allocation (draw->reader))) - break; - - if (ev->alloc_size > 0) - { - size = ev->alloc_size; - raxInsert (draw->rax, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr, GSIZE_TO_POINTER (size), NULL); - - if (last != &draw->fg) - { - gdk_cairo_set_source_rgba (cr, &draw->fg); - last = &draw->fg; - } - } - else - { - size = GPOINTER_TO_SIZE (raxFind (draw->rax, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr)); - if (size) - raxRemove (draw->rax, (guint8 *)&ev->alloc_addr, sizeof ev->alloc_addr, NULL); - - if (last != &draw->fg2) - { - gdk_cairo_set_source_rgba (cr, &draw->fg2); - last = &draw->fg2; - } - } - - l = log10 (size); - - x = (ev->frame.time - draw->begin_time) / (gdouble)draw->duration * draw->alloc.width; - - if (ev->alloc_size > 0) - y = midpt - ((l / log_max) * midpt); - else - y = midpt + ((l / log_max) * midpt); - - /* Fill immediately instead of batching draws so that - * we don't take a lot of memory to hold on to the - * path while drawing. - */ - cairo_rectangle (cr, x, y, 1, 1); - cairo_fill (cr); - } - - cairo_destroy (cr); - - g_task_return_boolean (task, TRUE); -} - -static void -draw_finished (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(SysprofMemprofVisualizer) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (object == NULL); - g_assert (G_IS_TASK (result)); - g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self)); - - if (g_task_propagate_boolean (G_TASK (result), &error)) - { - DrawContext *draw = g_task_get_task_data (G_TASK (result)); - - g_clear_pointer (&self->surface, cairo_surface_destroy); - - self->surface = g_steal_pointer (&draw->surface); - self->surface_w = draw->alloc.width; - self->surface_h = draw->alloc.height; - self->cached_max_alloc = draw->max_alloc; - self->cached_total_alloc = draw->total_alloc; - - gtk_widget_queue_draw (GTK_WIDGET (self)); - } -} - -static gboolean -sysprof_memprof_visualizer_begin_draw (SysprofMemprofVisualizer *self) -{ - g_autoptr(GTask) task = NULL; - GtkAllocation alloc; - DrawContext *draw; - - g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self)); - - self->queued_draw = 0; - - /* Make sure we even need to draw */ - gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); - if (self->reader == NULL || - !gtk_widget_get_visible (GTK_WIDGET (self)) || - !gtk_widget_get_mapped (GTK_WIDGET (self)) || - alloc.width == 0 || alloc.height == 0) - return G_SOURCE_REMOVE; - - /* Some GPUs (Intel) cannot deal with graphics textures larger than - * 8000x8000. So here we are going to cheat a bit and just use that as our - * max, and scale when drawing. The biggest issue here is that long term we - * need a tiling solution that lets us render lots of tiles and then draw - * them as necessary. - */ - if (alloc.width > 8000) - alloc.width = 8000; - - draw = g_slice_new0 (DrawContext); - draw->rax = raxNew (); - draw->alloc.width = alloc.width; - draw->alloc.height = alloc.height; - draw->reader = sysprof_capture_reader_copy (self->reader); - draw->begin_time = self->begin_time; - draw->duration = self->duration; - draw->scale = gtk_widget_get_scale_factor (GTK_WIDGET (self)); - draw->max_alloc = self->cached_max_alloc; - draw->total_alloc = self->cached_total_alloc; - - gdk_rgba_parse (&draw->fg, "rgba(246,97,81,1)"); - gdk_rgba_parse (&draw->fg2, "rgba(245,194,17,1)"); - - draw->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, - alloc.width * draw->scale, - alloc.height * draw->scale); - cairo_surface_set_device_scale (draw->surface, draw->scale, draw->scale); - - g_cancellable_cancel (self->cancellable); - g_clear_object (&self->cancellable); - self->cancellable = g_cancellable_new (); - - task = g_task_new (NULL, self->cancellable, draw_finished, g_object_ref (self)); - g_task_set_source_tag (task, sysprof_memprof_visualizer_begin_draw); - g_task_set_task_data (task, g_steal_pointer (&draw), (GDestroyNotify)draw_context_free); - - if (self->mode == MODE_ALLOCS) - g_task_run_in_thread (task, draw_alloc_worker); - else - g_task_run_in_thread (task, draw_total_worker); - - return G_SOURCE_REMOVE; -} - -static void -sysprof_memprof_visualizer_queue_redraw (SysprofMemprofVisualizer *self) -{ - g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self)); - - if (self->queued_draw == 0) - self->queued_draw = g_idle_add_full (G_PRIORITY_HIGH_IDLE, - (GSourceFunc) sysprof_memprof_visualizer_begin_draw, - g_object_ref (self), - g_object_unref); -} - -static void -sysprof_memprof_visualizer_size_allocate (GtkWidget *widget, - int width, - int height, - int baseline) -{ - sysprof_memprof_visualizer_queue_redraw (SYSPROF_MEMPROF_VISUALIZER (widget)); -} - -static void -sysprof_memprof_visualizer_dispose (GObject *object) -{ - SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)object; - - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - g_clear_pointer (&self->surface, cairo_surface_destroy); - g_clear_handle_id (&self->queued_draw, g_source_remove); - - G_OBJECT_CLASS (sysprof_memprof_visualizer_parent_class)->dispose (object); -} - -static void -sysprof_memprof_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofMemprofVisualizer *self = (SysprofMemprofVisualizer *)widget; - - g_assert (SYSPROF_IS_MEMPROF_VISUALIZER (self)); - g_assert (GTK_IS_SNAPSHOT (snapshot)); - - GTK_WIDGET_CLASS (sysprof_memprof_visualizer_parent_class)->snapshot (widget, snapshot); - - if (self->surface != NULL) - { - cairo_t *cr; - GtkAllocation alloc; - - gtk_widget_get_allocation (widget, &alloc); - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height)); - - cairo_save (cr); - cairo_rectangle (cr, 0, 0, alloc.width, alloc.height); - - /* We might be drawing an updated image in the background, and this - * will take our current surface (which is the wrong size) and draw - * it stretched to fit the allocation. That gives us *something* that - * represents the end result even if it is a bit blurry in the mean - * time. Allocators take a while to render anyway. - */ - if (self->surface_w != alloc.width || self->surface_h != alloc.height) - { - cairo_scale (cr, - (gdouble)alloc.width / (gdouble)self->surface_w, - (gdouble)alloc.height / (gdouble)self->surface_h); - } - - cairo_set_source_surface (cr, self->surface, 0, 0); - cairo_paint (cr); - cairo_restore (cr); - - cairo_destroy (cr); - } -} - -static void -sysprof_memprof_visualizer_class_init (SysprofMemprofVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass); - - object_class->dispose = sysprof_memprof_visualizer_dispose; - - widget_class->snapshot = sysprof_memprof_visualizer_snapshot; - widget_class->size_allocate = sysprof_memprof_visualizer_size_allocate; - - visualizer_class->set_reader = sysprof_memprof_visualizer_set_reader; -} - -static void -sysprof_memprof_visualizer_init (SysprofMemprofVisualizer *self) -{ -} diff --git a/src/libsysprof-ui/sysprof-memprof-visualizer.h b/src/libsysprof-ui/sysprof-memprof-visualizer.h deleted file mode 100644 index 04f078c8..00000000 --- a/src/libsysprof-ui/sysprof-memprof-visualizer.h +++ /dev/null @@ -1,33 +0,0 @@ -/* sysprof-memprof-visualizer.h - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-visualizer.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MEMPROF_VISUALIZER (sysprof_memprof_visualizer_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofMemprofVisualizer, sysprof_memprof_visualizer, SYSPROF, MEMPROF_VISUALIZER, SysprofVisualizer) - -SysprofVisualizer *sysprof_memprof_visualizer_new (gboolean total_allocs); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-model-filter.c b/src/libsysprof-ui/sysprof-model-filter.c deleted file mode 100644 index 48c9386e..00000000 --- a/src/libsysprof-ui/sysprof-model-filter.c +++ /dev/null @@ -1,497 +0,0 @@ -/* sysprof-model-filter.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-model-filter.h" - -typedef struct -{ - GSequenceIter *child_iter; - GSequenceIter *filter_iter; -} SysprofModelFilterItem; - -typedef struct -{ - /* The list we are filtering */ - GListModel *child_model; - - /* - * Both sequences point to the same SysprofModelFilterItem which - * contains cross-referencing stable GSequenceIter pointers. - * The child_seq is considered the "owner" and used to release - * allocated resources. - */ - GSequence *child_seq; - GSequence *filter_seq; - - /* - * Typical set of callback/closure/free function pointers and data. - * Called for child items to determine visibility state. - */ - SysprofModelFilterFunc filter_func; - gpointer filter_func_data; - GDestroyNotify filter_func_data_destroy; - - /* - * If set, we will not emit items-changed. This is useful during - * invalidation so that we can do a single emission for all items - * that have changed. - */ - guint supress_items_changed : 1; -} SysprofModelFilterPrivate; - -static void list_model_iface_init (GListModelInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofModelFilter, sysprof_model_filter, G_TYPE_OBJECT, 0, - G_ADD_PRIVATE (SysprofModelFilter) - G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, - list_model_iface_init)) - -enum { - PROP_0, - PROP_CHILD_MODEL, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; -static guint signal_id; - -static void -sysprof_model_filter_item_free (gpointer data) -{ - SysprofModelFilterItem *item = data; - - g_clear_pointer (&item->filter_iter, g_sequence_remove); - item->child_iter = NULL; - g_slice_free (SysprofModelFilterItem, item); -} - -static gboolean -sysprof_model_filter_default_filter_func (GObject *item, - gpointer user_data) -{ - return TRUE; -} - -/* - * Locates the next item in the filter sequence starting from - * the cross-reference found at @iter. If none are found, the - * end_iter for the filter sequence is returned. - * - * This returns an iter in the filter_sequence, not the child_seq. - * - * Returns: a #GSequenceIter from the filter sequence. - */ -static GSequenceIter * -find_next_visible_filter_iter (SysprofModelFilter *self, - GSequenceIter *iter) -{ - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - g_assert (SYSPROF_IS_MODEL_FILTER (self)); - g_assert (iter != NULL); - - for (; !g_sequence_iter_is_end (iter); iter = g_sequence_iter_next (iter)) - { - SysprofModelFilterItem *item = g_sequence_get (iter); - - g_assert (item->child_iter == iter); - g_assert (item->filter_iter == NULL || - g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq); - - if (item->filter_iter != NULL) - return item->filter_iter; - } - - return g_sequence_get_end_iter (priv->filter_seq); -} - -static void -sysprof_model_filter_child_model_items_changed (SysprofModelFilter *self, - guint position, - guint n_removed, - guint n_added, - GListModel *child_model) -{ - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - gboolean unblocked; - - g_assert (SYSPROF_IS_MODEL_FILTER (self)); - g_assert (G_IS_LIST_MODEL (child_model)); - g_assert (priv->child_model == child_model); - g_assert (position <= (guint)g_sequence_get_length (priv->child_seq)); - g_assert ((g_sequence_get_length (priv->child_seq) - n_removed + n_added) == - g_list_model_get_n_items (child_model)); - - unblocked = !priv->supress_items_changed; - - if (n_removed > 0) - { - GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position); - gint first_position = -1; - guint count = 0; - - g_assert (!g_sequence_iter_is_end (iter)); - - /* Small shortcut when all items are removed */ - if (n_removed == (guint)g_sequence_get_length (priv->child_seq)) - { - g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq), - g_sequence_get_end_iter (priv->child_seq)); - g_assert (g_sequence_is_empty (priv->child_seq)); - g_assert (g_sequence_is_empty (priv->filter_seq)); - goto add_new_items; - } - - for (guint i = 0; i < n_removed; i++) - { - GSequenceIter *to_remove = iter; - SysprofModelFilterItem *item = g_sequence_get (iter); - - g_assert (item != NULL); - g_assert (item->child_iter == iter); - g_assert (item->filter_iter == NULL || - g_sequence_iter_get_sequence (item->filter_iter) == priv->filter_seq); - - /* If this is visible, we need to notify about removal */ - if (unblocked && item->filter_iter != NULL) - { - if (first_position < 0) - first_position = g_sequence_iter_get_position (item->filter_iter); - - count++; - } - - /* Fetch the next while the iter is still valid */ - iter = g_sequence_iter_next (iter); - - /* Cascades into also removing from filter_seq. */ - g_sequence_remove (to_remove); - } - - if (unblocked && first_position >= 0) - g_list_model_items_changed (G_LIST_MODEL (self), first_position, count, 0); - } - -add_new_items: - - if (n_added > 0) - { - GSequenceIter *iter = g_sequence_get_iter_at_pos (priv->child_seq, position); - GSequenceIter *filter_iter = find_next_visible_filter_iter (self, iter); - guint filter_position = g_sequence_iter_get_position (filter_iter); - guint count = 0; - - /* Walk backwards to insert items into the filter list so that - * we can use the same filter_position for each items-changed - * signal emission. - */ - for (guint i = position + n_added; i > position; i--) - { - g_autoptr(GObject) instance = NULL; - SysprofModelFilterItem *item; - - item = g_slice_new0 (SysprofModelFilterItem); - item->filter_iter = NULL; - item->child_iter = g_sequence_insert_before (iter, item); - - instance = g_list_model_get_item (child_model, i - 1); - g_assert (G_IS_OBJECT (instance)); - - /* Check if this item is visible */ - if (priv->filter_func (instance, priv->filter_func_data)) - { - item->filter_iter = g_sequence_insert_before (filter_iter, item); - - /* Use this in the future for relative positioning */ - filter_iter = item->filter_iter; - - count++; - } - - /* Insert next item before this */ - iter = item->child_iter; - } - - if (unblocked && count) - g_list_model_items_changed (G_LIST_MODEL (self), filter_position, 0, count); - } - - g_assert ((guint)g_sequence_get_length (priv->child_seq) == - g_list_model_get_n_items (child_model)); -} - -static void -sysprof_model_filter_finalize (GObject *object) -{ - SysprofModelFilter *self = (SysprofModelFilter *)object; - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - g_clear_pointer (&priv->child_seq, g_sequence_free); - g_clear_pointer (&priv->filter_seq, g_sequence_free); - - if (priv->filter_func_data_destroy) - { - g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy); - priv->filter_func_data_destroy = NULL; - } - - g_clear_object (&priv->child_model); - - G_OBJECT_CLASS (sysprof_model_filter_parent_class)->finalize (object); -} - -static void -sysprof_model_filter_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofModelFilter *self = SYSPROF_MODEL_FILTER (object); - - switch (prop_id) - { - case PROP_CHILD_MODEL: - g_value_set_object (value, sysprof_model_filter_get_child_model (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_model_filter_class_init (SysprofModelFilterClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_model_filter_finalize; - object_class->get_property = sysprof_model_filter_get_property; - - properties [PROP_CHILD_MODEL] = - g_param_spec_object ("child-model", - "Child Model", - "The child model being filtered.", - G_TYPE_LIST_MODEL, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - signal_id = g_signal_lookup ("items-changed", SYSPROF_TYPE_MODEL_FILTER); -} - -static void -sysprof_model_filter_init (SysprofModelFilter *self) -{ - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - priv->filter_func = sysprof_model_filter_default_filter_func; - priv->child_seq = g_sequence_new (sysprof_model_filter_item_free); - priv->filter_seq = g_sequence_new (NULL); -} - -static GType -sysprof_model_filter_get_item_type (GListModel *model) -{ - SysprofModelFilter *self = (SysprofModelFilter *)model; - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - g_assert (SYSPROF_IS_MODEL_FILTER (self)); - - return g_list_model_get_item_type (priv->child_model); -} - -static guint -sysprof_model_filter_get_n_items (GListModel *model) -{ - SysprofModelFilter *self = (SysprofModelFilter *)model; - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - g_assert (SYSPROF_IS_MODEL_FILTER (self)); - g_assert (priv->filter_seq != NULL); - - return g_sequence_get_length (priv->filter_seq); -} - -static gpointer -sysprof_model_filter_get_item (GListModel *model, - guint position) -{ - SysprofModelFilter *self = (SysprofModelFilter *)model; - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - SysprofModelFilterItem *item; - GSequenceIter *iter; - guint child_position; - - g_assert (SYSPROF_IS_MODEL_FILTER (self)); - g_assert (position < (guint)g_sequence_get_length (priv->filter_seq)); - - iter = g_sequence_get_iter_at_pos (priv->filter_seq, position); - g_assert (!g_sequence_iter_is_end (iter)); - - item = g_sequence_get (iter); - g_assert (item != NULL); - g_assert (item->filter_iter == iter); - g_assert (item->child_iter != NULL); - g_assert (g_sequence_iter_get_sequence (item->child_iter) == priv->child_seq); - - child_position = g_sequence_iter_get_position (item->child_iter); - - return g_list_model_get_item (priv->child_model, child_position); -} - -static void -list_model_iface_init (GListModelInterface *iface) -{ - iface->get_item_type = sysprof_model_filter_get_item_type; - iface->get_n_items = sysprof_model_filter_get_n_items; - iface->get_item = sysprof_model_filter_get_item; -} - -SysprofModelFilter * -sysprof_model_filter_new (GListModel *child_model) -{ - SysprofModelFilter *ret; - SysprofModelFilterPrivate *priv; - - g_return_val_if_fail (G_IS_LIST_MODEL (child_model), NULL); - - ret = g_object_new (SYSPROF_TYPE_MODEL_FILTER, NULL); - priv = sysprof_model_filter_get_instance_private (ret); - priv->child_model = g_object_ref (child_model); - - g_signal_connect_object (child_model, - "items-changed", - G_CALLBACK (sysprof_model_filter_child_model_items_changed), - ret, - G_CONNECT_SWAPPED); - - sysprof_model_filter_invalidate (ret); - - return ret; -} - -/** - * sysprof_model_filter_get_child_model: - * @self: A #SysprofModelFilter - * - * Gets the child model that is being filtered. - * - * Returns: (transfer none): A #GListModel. - */ -GListModel * -sysprof_model_filter_get_child_model (SysprofModelFilter *self) -{ - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_MODEL_FILTER (self), NULL); - - return priv->child_model; -} - -void -sysprof_model_filter_invalidate (SysprofModelFilter *self) -{ - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - guint n_items; - - g_return_if_fail (SYSPROF_IS_MODEL_FILTER (self)); - - /* We block emission while in invalidate so that we can use - * a single larger items-changed rather lots of small emissions. - */ - priv->supress_items_changed = TRUE; - - /* First determine how many items we need to synthesize as a removal */ - n_items = g_sequence_get_length (priv->filter_seq); - - /* - * If we have a child store, we want to rebuild our list of items - * from scratch, so just remove everything. - */ - if (!g_sequence_is_empty (priv->child_seq)) - g_sequence_remove_range (g_sequence_get_begin_iter (priv->child_seq), - g_sequence_get_end_iter (priv->child_seq)); - - g_assert (g_sequence_is_empty (priv->child_seq)); - g_assert (g_sequence_is_empty (priv->filter_seq)); - g_assert (!priv->child_model || G_IS_LIST_MODEL (priv->child_model)); - - /* - * Now add the new items by synthesizing the addition of all the - * itmes in the list. - */ - if (priv->child_model != NULL) - { - guint child_n_items; - - /* - * Now add all the items as one shot to our list so that - * we get populate our sequence and filter sequence. - */ - child_n_items = g_list_model_get_n_items (priv->child_model); - sysprof_model_filter_child_model_items_changed (self, 0, 0, child_n_items, priv->child_model); - - g_assert ((guint)g_sequence_get_length (priv->child_seq) == child_n_items); - g_assert ((guint)g_sequence_get_length (priv->filter_seq) <= child_n_items); - } - - priv->supress_items_changed = FALSE; - - /* Now that we've updated our sequences, notify of all the changes - * as a single series of updates to the consumers. - */ - if (n_items > 0 || !g_sequence_is_empty (priv->filter_seq)) - g_list_model_items_changed (G_LIST_MODEL (self), - 0, - n_items, - g_sequence_get_length (priv->filter_seq)); -} - -void -sysprof_model_filter_set_filter_func (SysprofModelFilter *self, - SysprofModelFilterFunc filter_func, - gpointer filter_func_data, - GDestroyNotify filter_func_data_destroy) -{ - SysprofModelFilterPrivate *priv = sysprof_model_filter_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_MODEL_FILTER (self)); - g_return_if_fail (filter_func || (!filter_func_data && !filter_func_data_destroy)); - - if (priv->filter_func_data_destroy != NULL) - g_clear_pointer (&priv->filter_func_data, priv->filter_func_data_destroy); - - if (filter_func != NULL) - { - priv->filter_func = filter_func; - priv->filter_func_data = filter_func_data; - priv->filter_func_data_destroy = filter_func_data_destroy; - } - else - { - priv->filter_func = sysprof_model_filter_default_filter_func; - priv->filter_func_data = NULL; - priv->filter_func_data_destroy = NULL; - } - - sysprof_model_filter_invalidate (self); -} diff --git a/src/libsysprof-ui/sysprof-model-filter.h b/src/libsysprof-ui/sysprof-model-filter.h deleted file mode 100644 index a1b97bd4..00000000 --- a/src/libsysprof-ui/sysprof-model-filter.h +++ /dev/null @@ -1,60 +0,0 @@ -/* sysprof-model-filter.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MODEL_FILTER (sysprof_model_filter_get_type()) - -typedef gboolean (*SysprofModelFilterFunc) (GObject *object, - gpointer user_data); - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofModelFilter, sysprof_model_filter, SYSPROF, MODEL_FILTER, GObject) - -struct _SysprofModelFilterClass -{ - GObjectClass parent_class; - - gpointer padding[8]; -}; - -SYSPROF_AVAILABLE_IN_ALL -SysprofModelFilter *sysprof_model_filter_new (GListModel *child_model); -SYSPROF_AVAILABLE_IN_ALL -GListModel *sysprof_model_filter_get_child_model (SysprofModelFilter *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_model_filter_invalidate (SysprofModelFilter *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_model_filter_set_filter_func (SysprofModelFilter *self, - SysprofModelFilterFunc filter_func, - gpointer filter_func_data, - GDestroyNotify filter_func_data_destroy); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-netdev-aid.c b/src/libsysprof-ui/sysprof-netdev-aid.c deleted file mode 100644 index 5f1c9f2c..00000000 --- a/src/libsysprof-ui/sysprof-netdev-aid.c +++ /dev/null @@ -1,266 +0,0 @@ -/* sysprof-netdev-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-netdev-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-duplex-visualizer.h" -#include "sysprof-netdev-aid.h" - -struct _SysprofNetdevAid -{ - SysprofAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; -} Present; - -G_DEFINE_TYPE (SysprofNetdevAid, sysprof_netdev_aid, SYSPROF_TYPE_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -/** - * sysprof_netdev_aid_new: - * - * Create a new #SysprofNetdevAid. - * - * Returns: (transfer full): a newly created #SysprofNetdevAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_netdev_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_NETDEV_AID, NULL); -} - -static void -sysprof_netdev_aid_prepare (SysprofAid *self, - SysprofProfiler *profiler) -{ - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_NETDEV_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_netdev_source_new (); - sysprof_profiler_add_source (profiler, source); -} - -static bool -collect_netdev_counters (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - GArray *counters = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (counters != NULL); - - for (guint i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *counter = &def->counters[i]; - - if (strcmp (counter->category, "Network") == 0 && - (g_str_has_prefix (counter->name, "RX Bytes") || - g_str_has_prefix (counter->name, "TX Bytes"))) - g_array_append_vals (counters, counter, 1); - } - - return TRUE; -} - -static void -sysprof_netdev_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *present = task_data; - g_autoptr(GArray) counters = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_NETDEV_AID (source_object)); - g_assert (present != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - sysprof_capture_cursor_foreach (present->cursor, collect_netdev_counters, counters); - g_task_return_pointer (task, - g_steal_pointer (&counters), - (GDestroyNotify) g_array_unref); -} - -static guint -find_other_id (GArray *counters, - const gchar *rx) -{ - g_autofree gchar *other = NULL; - - g_assert (counters); - g_assert (rx != NULL); - - other = g_strdup (rx); - other[0] = 'T'; - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *c = &g_array_index (counters, SysprofCaptureCounter, i); - - if (g_str_equal (c->name, other)) - return c->id; - } - - return 0; -} - -static void -sysprof_netdev_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_CTRDEF }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_NETDEV_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (1, types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_netdev_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_netdev_aid_present_worker); -} - -static gboolean -sysprof_netdev_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) counters = NULL; - Present *present; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_TASK (result)); - - present = g_task_get_task_data (G_TASK (result)); - - if ((counters = g_task_propagate_pointer (G_TASK (result), error))) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - SysprofVisualizerGroup *group; - - group = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "title", _("Network"), - "visible", TRUE, - NULL); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *ctr = &g_array_index (counters, SysprofCaptureCounter, i); - - if (g_str_has_prefix (ctr->name, "RX Bytes")) - { - g_autofree gchar *title = NULL; - gboolean is_combined; - GtkWidget *row; - GdkRGBA rgba; - guint other_id; - - if (!(other_id = find_other_id (counters, ctr->name))) - continue; - - is_combined = g_str_equal (ctr->description, "Combined"); - - if (is_combined) - title = g_strdup ("Network Bytes (All)"); - else - title = g_strdup_printf ("Network Bytes%s", ctr->name + strlen ("RX Bytes")); - - row = g_object_new (SYSPROF_TYPE_DUPLEX_VISUALIZER, - "title", title, - "height-request", 35, - "visible", is_combined, - NULL); - sysprof_color_cycle_next (cycle, &rgba); - sysprof_duplex_visualizer_set_counters (SYSPROF_DUPLEX_VISUALIZER (row), ctr->id, other_id); - sysprof_duplex_visualizer_set_colors (SYSPROF_DUPLEX_VISUALIZER (row), &rgba, &rgba); - sysprof_visualizer_group_insert (group, SYSPROF_VISUALIZER (row), is_combined ? 0 : -1, !is_combined); - } - } - - if (counters->len > 0) - sysprof_display_add_group (present->display, group); - else - g_object_unref (g_object_ref_sink (group)); - } - - return counters != NULL; -} - -static void -sysprof_netdev_aid_class_init (SysprofNetdevAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->prepare = sysprof_netdev_aid_prepare; - aid_class->present_async = sysprof_netdev_aid_present_async; - aid_class->present_finish = sysprof_netdev_aid_present_finish; -} - -static void -sysprof_netdev_aid_init (SysprofNetdevAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Network")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "preferences-system-network-symbolic"); -} diff --git a/src/libsysprof-ui/sysprof-netdev-aid.h b/src/libsysprof-ui/sysprof-netdev-aid.h deleted file mode 100644 index 6bff1765..00000000 --- a/src/libsysprof-ui/sysprof-netdev-aid.h +++ /dev/null @@ -1,33 +0,0 @@ -/* sysprof-netdev-aid.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-aid.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_NETDEV_AID (sysprof_netdev_aid_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofNetdevAid, sysprof_netdev_aid, SYSPROF, NETDEV_AID, SysprofAid) - -SysprofAid *sysprof_netdev_aid_new (void); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-notebook.c b/src/libsysprof-ui/sysprof-notebook.c deleted file mode 100644 index 1a040c28..00000000 --- a/src/libsysprof-ui/sysprof-notebook.c +++ /dev/null @@ -1,561 +0,0 @@ -/* sysprof-notebook.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-notebook" - -#include "config.h" - -#include "sysprof-display.h" -#include "sysprof-notebook.h" -#include "sysprof-tab.h" -#include "sysprof-ui-private.h" - -typedef struct -{ - GtkNotebook *notebook; - guint always_show_tabs : 1; -} SysprofNotebookPrivate; - -static void buildable_iface_init (GtkBuildableIface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofNotebook, sysprof_notebook, GTK_TYPE_WIDGET, - G_ADD_PRIVATE (SysprofNotebook) - G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)) - -enum { - PROP_0, - PROP_ALWAYS_SHOW_TABS, - PROP_CAN_REPLAY, - PROP_CAN_SAVE, - PROP_CURRENT, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -/** - * sysprof_notebook_new: - * - * Create a new #SysprofNotebook. - * - * Returns: (transfer full): a newly created #SysprofNotebook - * - * Since: 3.34 - */ -GtkWidget * -sysprof_notebook_new (void) -{ - return g_object_new (SYSPROF_TYPE_NOTEBOOK, NULL); -} - -static void -sysprof_notebook_notify_can_save_cb (SysprofNotebook *self, - GParamSpec *pspec, - SysprofDisplay *display) -{ - g_assert (SYSPROF_IS_NOTEBOOK (self)); - g_assert (SYSPROF_IS_DISPLAY (display)); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_SAVE]); -} - -static void -sysprof_notebook_notify_can_replay_cb (SysprofNotebook *self, - GParamSpec *pspec, - SysprofDisplay *display) -{ - g_assert (SYSPROF_IS_NOTEBOOK (self)); - g_assert (SYSPROF_IS_DISPLAY (display)); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_REPLAY]); -} - -static void -sysprof_notebook_page_added (SysprofNotebook *self, - GtkWidget *child, - guint page_num, - GtkNotebook *notebook) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_assert (SYSPROF_IS_NOTEBOOK (self)); - g_assert (GTK_IS_WIDGET (child)); - g_assert (GTK_IS_NOTEBOOK (notebook)); - - gtk_notebook_set_show_tabs (notebook, - (priv->always_show_tabs || - gtk_notebook_get_n_pages (notebook) > 1)); - - if (SYSPROF_IS_DISPLAY (child)) - { - GtkWidget *tab = sysprof_tab_new (SYSPROF_DISPLAY (child)); - - gtk_notebook_set_tab_label (notebook, child, tab); - gtk_notebook_set_tab_reorderable (notebook, child, TRUE); - - g_signal_connect_object (child, - "notify::can-replay", - G_CALLBACK (sysprof_notebook_notify_can_replay_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (child, - "notify::can-save", - G_CALLBACK (sysprof_notebook_notify_can_save_cb), - self, - G_CONNECT_SWAPPED); - - g_object_notify_by_pspec (G_OBJECT (notebook), properties [PROP_CAN_REPLAY]); - g_object_notify_by_pspec (G_OBJECT (notebook), properties [PROP_CAN_SAVE]); - g_object_notify_by_pspec (G_OBJECT (notebook), properties [PROP_CURRENT]); - - _sysprof_display_focus_record (SYSPROF_DISPLAY (child)); - } -} - -static void -sysprof_notebook_page_removed (SysprofNotebook *self, - GtkWidget *child, - guint page_num, - GtkNotebook *notebook) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_assert (SYSPROF_IS_NOTEBOOK (self)); - g_assert (GTK_IS_WIDGET (child)); - g_assert (GTK_IS_NOTEBOOK (notebook)); - - if (gtk_widget_in_destruction (GTK_WIDGET (notebook))) - return; - - if (gtk_notebook_get_n_pages (notebook) == 0) - { - child = sysprof_display_new (); - gtk_notebook_append_page (notebook, child, NULL); - gtk_widget_show (child); - - g_signal_handlers_disconnect_by_func (child, - G_CALLBACK (sysprof_notebook_notify_can_save_cb), - notebook); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_REPLAY]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_SAVE]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT]); - } - - gtk_notebook_set_show_tabs (notebook, - (priv->always_show_tabs || - gtk_notebook_get_n_pages (notebook) > 1)); -} - -static void -sysprof_notebook_switch_page (SysprofNotebook *self, - GtkWidget *widget, - guint page, - GtkNotebook *notebook) -{ - g_assert (SYSPROF_IS_NOTEBOOK (self)); - g_assert (GTK_IS_NOTEBOOK (notebook)); - g_assert (GTK_IS_WIDGET (widget)); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_REPLAY]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_SAVE]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT]); -} - -static void -sysprof_notebook_dispose (GObject *object) -{ - SysprofNotebook *self = (SysprofNotebook *)object; - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - if (priv->notebook) - { - gtk_widget_unparent (GTK_WIDGET (priv->notebook)); - priv->notebook = NULL; - } - - G_OBJECT_CLASS (sysprof_notebook_parent_class)->dispose (object); -} - -static void -sysprof_notebook_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofNotebook *self = (SysprofNotebook *)object; - - switch (prop_id) - { - case PROP_ALWAYS_SHOW_TABS: - g_value_set_boolean (value, sysprof_notebook_get_always_show_tabs (self)); - break; - - case PROP_CAN_REPLAY: - g_value_set_boolean (value, sysprof_notebook_get_can_replay (self)); - break; - - case PROP_CAN_SAVE: - g_value_set_boolean (value, sysprof_notebook_get_can_save (self)); - break; - - case PROP_CURRENT: - g_value_set_object (value, sysprof_notebook_get_current (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_notebook_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofNotebook *self = (SysprofNotebook *)object; - - switch (prop_id) - { - case PROP_ALWAYS_SHOW_TABS: - sysprof_notebook_set_always_show_tabs (self, g_value_get_boolean (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_notebook_class_init (SysprofNotebookClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_notebook_dispose; - object_class->get_property = sysprof_notebook_get_property; - object_class->set_property = sysprof_notebook_set_property; - - properties [PROP_ALWAYS_SHOW_TABS] = - g_param_spec_boolean ("always-show-tabs", - "Always Show Tabs", - "Always Show Tabs", - FALSE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CAN_REPLAY] = - g_param_spec_boolean ("can-replay", - "Can Replay", - "If the current display can replay a recording", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CAN_SAVE] = - g_param_spec_boolean ("can-save", - "Can Save", - "If the current display can save a recording", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CURRENT] = - g_param_spec_object ("current", - "Current", - "The current display", - SYSPROF_TYPE_DISPLAY, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); -} - -static void -sysprof_notebook_init (SysprofNotebook *self) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - priv->notebook = GTK_NOTEBOOK (gtk_notebook_new ()); - gtk_widget_set_parent (GTK_WIDGET (priv->notebook), GTK_WIDGET (self)); - - gtk_notebook_set_show_border (priv->notebook, FALSE); - gtk_notebook_set_scrollable (priv->notebook, TRUE); - gtk_notebook_popup_enable (priv->notebook); - - g_signal_connect_object (priv->notebook, - "page-added", - G_CALLBACK (sysprof_notebook_page_added), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->notebook, - "page-removed", - G_CALLBACK (sysprof_notebook_page_removed), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (priv->notebook, - "switch-page", - G_CALLBACK (sysprof_notebook_switch_page), - self, - G_CONNECT_SWAPPED | G_CONNECT_AFTER); -} - -void -sysprof_notebook_close_current (SysprofNotebook *self) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - gint page; - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - - if ((page = gtk_notebook_get_current_page (priv->notebook)) >= 0) - gtk_notebook_remove_page (priv->notebook, page); -} - -void -sysprof_notebook_open (SysprofNotebook *self, - GFile *file) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - SysprofDisplay *display = NULL; - int page; - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - g_return_if_fail (g_file_is_native (file)); - - for (page = 0; page < sysprof_notebook_get_n_pages (self); page++) - { - SysprofDisplay *child = sysprof_notebook_get_nth_page (self, page); - - if (sysprof_display_is_empty (child)) - { - display = child; - break; - } - } - - if (display == NULL) - { - display = SYSPROF_DISPLAY (sysprof_display_new ()); - page = sysprof_notebook_append (self, display); - } - else - { - page = gtk_notebook_page_num (priv->notebook, GTK_WIDGET (display)); - } - - sysprof_notebook_set_current_page (self, page); - - sysprof_display_open (SYSPROF_DISPLAY (display), file); -} - -SysprofDisplay * -sysprof_notebook_get_current (SysprofNotebook *self) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - gint page; - - g_assert (SYSPROF_IS_NOTEBOOK (self)); - - if ((page = gtk_notebook_get_current_page (priv->notebook)) >= 0) - return SYSPROF_DISPLAY (gtk_notebook_get_nth_page (priv->notebook, page)); - - return NULL; -} - -void -sysprof_notebook_save (SysprofNotebook *self) -{ - SysprofDisplay *display; - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - - if ((display = sysprof_notebook_get_current (self))) - sysprof_display_save (display); -} - -gboolean -sysprof_notebook_get_can_save (SysprofNotebook *self) -{ - SysprofDisplay *display; - - g_return_val_if_fail (SYSPROF_IS_NOTEBOOK (self), FALSE); - - if ((display = sysprof_notebook_get_current (self))) - return sysprof_display_get_can_save (display); - - return FALSE; -} - -gboolean -sysprof_notebook_get_can_replay (SysprofNotebook *self) -{ - SysprofDisplay *display; - - g_return_val_if_fail (SYSPROF_IS_NOTEBOOK (self), FALSE); - - if ((display = sysprof_notebook_get_current (self))) - return sysprof_display_get_can_replay (display); - - return FALSE; -} - -void -sysprof_notebook_replay (SysprofNotebook *self) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - SysprofDisplay *display; - SysprofDisplay *replay; - gint page; - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - - if (!(display = sysprof_notebook_get_current (self)) || - !sysprof_display_get_can_replay (display) || - !(replay = sysprof_display_replay (display))) - return; - - g_return_if_fail (SYSPROF_IS_DISPLAY (replay)); - - gtk_widget_show (GTK_WIDGET (replay)); - gtk_notebook_append_page (priv->notebook, GTK_WIDGET (replay), NULL); - page = gtk_notebook_page_num (priv->notebook, GTK_WIDGET (replay)); - gtk_notebook_set_current_page (priv->notebook, page); -} - -void -sysprof_notebook_add_profiler (SysprofNotebook *self, - SysprofProfiler *profiler) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - GtkWidget *display; - gint page; - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - g_return_if_fail (SYSPROF_IS_PROFILER (profiler)); - - display = sysprof_display_new_for_profiler (profiler); - - gtk_widget_show (display); - gtk_notebook_append_page (priv->notebook, GTK_WIDGET (display), NULL); - page = gtk_notebook_page_num (priv->notebook, display); - gtk_notebook_set_current_page (priv->notebook, page); -} - -gboolean -sysprof_notebook_get_always_show_tabs (SysprofNotebook *self) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_NOTEBOOK (self), FALSE); - - return priv->always_show_tabs; -} - -void -sysprof_notebook_set_always_show_tabs (SysprofNotebook *self, - gboolean always_show_tabs) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - - always_show_tabs = !!always_show_tabs; - - if (always_show_tabs != priv->always_show_tabs) - { - priv->always_show_tabs = always_show_tabs; - gtk_notebook_set_show_tabs (priv->notebook, - (priv->always_show_tabs || - gtk_notebook_get_n_pages (priv->notebook) > 1)); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ALWAYS_SHOW_TABS]); - } -} - -static void -sysprof_notebook_add_child (GtkBuildable *buildable, - GtkBuilder *builder, - GObject *child, - const char *type) -{ - SysprofNotebook *self = (SysprofNotebook *)buildable; - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_assert (SYSPROF_IS_NOTEBOOK (self)); - - if (SYSPROF_IS_DISPLAY (child)) - gtk_notebook_append_page (priv->notebook, GTK_WIDGET (child), NULL); - else - g_warning ("Cannot add child of type %s to %s", - G_OBJECT_TYPE_NAME (child), - G_OBJECT_TYPE_NAME (self)); -} - -static void -buildable_iface_init (GtkBuildableIface *iface) -{ - iface->add_child = sysprof_notebook_add_child; -} - -guint -sysprof_notebook_get_n_pages (SysprofNotebook *self) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_NOTEBOOK (self), 0); - - return gtk_notebook_get_n_pages (priv->notebook); -} - -SysprofDisplay * -sysprof_notebook_get_nth_page (SysprofNotebook *self, - guint nth) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_NOTEBOOK (self), NULL); - - return SYSPROF_DISPLAY (gtk_notebook_get_nth_page (priv->notebook, nth)); -} - -void -sysprof_notebook_set_current_page (SysprofNotebook *self, - int nth) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_NOTEBOOK (self)); - - gtk_notebook_set_current_page (priv->notebook, nth); -} - -int -sysprof_notebook_append (SysprofNotebook *self, - SysprofDisplay *display) -{ - SysprofNotebookPrivate *priv = sysprof_notebook_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_NOTEBOOK (self), -1); - g_return_val_if_fail (SYSPROF_IS_DISPLAY (display), -1); - - return gtk_notebook_append_page (priv->notebook, GTK_WIDGET (display), NULL); -} diff --git a/src/libsysprof-ui/sysprof-notebook.h b/src/libsysprof-ui/sysprof-notebook.h deleted file mode 100644 index 943dc1f1..00000000 --- a/src/libsysprof-ui/sysprof-notebook.h +++ /dev/null @@ -1,85 +0,0 @@ -/* sysprof-notebook.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include -#include - -#include "sysprof-display.h" -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_NOTEBOOK (sysprof_notebook_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofNotebook, sysprof_notebook, SYSPROF, NOTEBOOK, GtkWidget) - -struct _SysprofNotebookClass -{ - GtkWidgetClass parent_class; - - /*< private >*/ - gpointer _reserved[16]; -}; - -SYSPROF_AVAILABLE_IN_ALL -GtkWidget *sysprof_notebook_new (void); -SYSPROF_AVAILABLE_IN_ALL -SysprofDisplay *sysprof_notebook_get_current (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_close_current (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_open (SysprofNotebook *self, - GFile *file); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_save (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_notebook_get_can_save (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_replay (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_notebook_get_can_replay (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_add_profiler (SysprofNotebook *self, - SysprofProfiler *profiler); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_notebook_get_always_show_tabs (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_set_always_show_tabs (SysprofNotebook *self, - gboolean always_show_tabs); -SYSPROF_AVAILABLE_IN_ALL -guint sysprof_notebook_get_n_pages (SysprofNotebook *self); -SYSPROF_AVAILABLE_IN_ALL -SysprofDisplay *sysprof_notebook_get_nth_page (SysprofNotebook *self, - guint nth); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_notebook_set_current_page (SysprofNotebook *self, - int page); -SYSPROF_AVAILABLE_IN_ALL -int sysprof_notebook_append (SysprofNotebook *self, - SysprofDisplay *display); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-page.c b/src/libsysprof-ui/sysprof-page.c deleted file mode 100644 index 45f61152..00000000 --- a/src/libsysprof-ui/sysprof-page.c +++ /dev/null @@ -1,256 +0,0 @@ -/* sysprof-page.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-page" - -#include "config.h" - -#include "sysprof-display.h" -#include "sysprof-page.h" -#include "sysprof-ui-private.h" - -typedef struct -{ - gchar *title; -} SysprofPagePrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofPage, sysprof_page, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_TITLE, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -/** - * sysprof_page_new: - * - * Create a new #SysprofPage. - * - * Returns: (transfer full) (type SysprofPage): a newly created #SysprofPage - */ -GtkWidget * -sysprof_page_new (void) -{ - return g_object_new (SYSPROF_TYPE_PAGE, NULL); -} - -const gchar * -sysprof_page_get_title (SysprofPage *self) -{ - SysprofPagePrivate *priv = sysprof_page_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_PAGE (self), NULL); - - return priv->title; -} - -void -sysprof_page_set_title (SysprofPage *self, - const gchar *title) -{ - SysprofPagePrivate *priv = sysprof_page_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_PAGE (self)); - - if (g_strcmp0 (priv->title, title) != 0) - { - g_free (priv->title); - priv->title = g_strdup (title); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); - } -} - -static void -sysprof_page_real_load_async (SysprofPage *self, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *condition, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_task_report_new_error (self, callback, user_data, - sysprof_page_load_async, - G_IO_ERROR, - G_IO_ERROR_NOT_SUPPORTED, - "Operation not supported"); -} - -static gboolean -sysprof_page_real_load_finish (SysprofPage *self, - GAsyncResult *result, - GError **error) -{ - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_page_dispose (GObject *object) -{ - SysprofPage *self = (SysprofPage *)object; - SysprofPagePrivate *priv = sysprof_page_get_instance_private (self); - GtkWidget *child; - - g_clear_pointer (&priv->title, g_free); - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - gtk_widget_unparent (child); - - G_OBJECT_CLASS (sysprof_page_parent_class)->dispose (object); -} - -static void -sysprof_page_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofPage *self = SYSPROF_PAGE (object); - - switch (prop_id) - { - case PROP_TITLE: - g_value_set_string (value, sysprof_page_get_title (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_page_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofPage *self = SYSPROF_PAGE (object); - - switch (prop_id) - { - case PROP_TITLE: - sysprof_page_set_title (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_page_class_init (SysprofPageClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_page_dispose; - object_class->get_property = sysprof_page_get_property; - object_class->set_property = sysprof_page_set_property; - - klass->load_async = sysprof_page_real_load_async; - klass->load_finish = sysprof_page_real_load_finish; - - properties [PROP_TITLE] = - g_param_spec_string ("title", - "Title", - "The title for the page", - NULL, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); -} - -static void -sysprof_page_init (SysprofPage *self) -{ - gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE); -} - -/** - * sysprof_page_load_async: - * @condition: (nullable): a #sysprofCaptureCondition or %NULL - * @cancellable: (nullable): a #GCancellable or %NULL - * - * Since: 3.34 - */ -void -sysprof_page_load_async (SysprofPage *self, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *condition, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_return_if_fail (SYSPROF_IS_PAGE (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - SYSPROF_PAGE_GET_CLASS (self)->load_async (self, reader, selection, condition, cancellable, callback, user_data); -} - -gboolean -sysprof_page_load_finish (SysprofPage *self, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_PAGE (self), FALSE); - g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); - - return SYSPROF_PAGE_GET_CLASS (self)->load_finish (self, result, error); -} - -void -sysprof_page_set_size_group (SysprofPage *self, - GtkSizeGroup *size_group) -{ - g_return_if_fail (SYSPROF_IS_PAGE (self)); - g_return_if_fail (!size_group || GTK_IS_SIZE_GROUP (size_group)); - - if (SYSPROF_PAGE_GET_CLASS (self)->set_size_group) - SYSPROF_PAGE_GET_CLASS (self)->set_size_group (self, size_group); -} - -void -sysprof_page_set_hadjustment (SysprofPage *self, - GtkAdjustment *hadjustment) -{ - g_return_if_fail (SYSPROF_IS_PAGE (self)); - g_return_if_fail (!hadjustment || GTK_IS_ADJUSTMENT (hadjustment)); - - if (SYSPROF_PAGE_GET_CLASS (self)->set_hadjustment) - SYSPROF_PAGE_GET_CLASS (self)->set_hadjustment (self, hadjustment); -} - -void -sysprof_page_reload (SysprofPage *self) -{ - GtkWidget *display; - - g_return_if_fail (SYSPROF_IS_PAGE (self)); - - if ((display = gtk_widget_get_ancestor (GTK_WIDGET (self), SYSPROF_TYPE_DISPLAY))) - _sysprof_display_reload_page (SYSPROF_DISPLAY (display), self); -} diff --git a/src/libsysprof-ui/sysprof-page.h b/src/libsysprof-ui/sysprof-page.h deleted file mode 100644 index 22d55044..00000000 --- a/src/libsysprof-ui/sysprof-page.h +++ /dev/null @@ -1,88 +0,0 @@ -/* sysprof-page.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PAGE (sysprof_page_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofPage, sysprof_page, SYSPROF, PAGE, GtkWidget) - -struct _SysprofPageClass -{ - GtkWidgetClass parent_class; - - void (*load_async) (SysprofPage *self, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *condition, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - gboolean (*load_finish) (SysprofPage *self, - GAsyncResult *result, - GError **error); - void (*set_hadjustment) (SysprofPage *self, - GtkAdjustment *hadjustment); - void (*set_size_group) (SysprofPage *self, - GtkSizeGroup *size_group); - - /*< private >*/ - gpointer _reserved[16]; -}; - -SYSPROF_AVAILABLE_IN_ALL -GtkWidget *sysprof_page_new (void); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_page_load_async (SysprofPage *self, - SysprofCaptureReader *reader, - SysprofSelection *selection, - SysprofCaptureCondition *condition, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_page_load_finish (SysprofPage *self, - GAsyncResult *result, - GError **error); -SYSPROF_AVAILABLE_IN_3_36 -void sysprof_page_reload (SysprofPage *self); -SYSPROF_AVAILABLE_IN_ALL -const gchar *sysprof_page_get_title (SysprofPage *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_page_set_title (SysprofPage *self, - const gchar *title); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_page_set_hadjustment (SysprofPage *self, - GtkAdjustment *hadjustment); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_page_set_size_group (SysprofPage *self, - GtkSizeGroup *size_group); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-process-model-row.c b/src/libsysprof-ui/sysprof-process-model-row.c deleted file mode 100644 index aa046f39..00000000 --- a/src/libsysprof-ui/sysprof-process-model-row.c +++ /dev/null @@ -1,258 +0,0 @@ -/* sysprof-process-model-row.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-process-model-row" - -#include "config.h" - -#include "sysprof-process-model-row.h" - -typedef struct -{ - SysprofProcessModelItem *item; - - GtkLabel *args_label; - GtkLabel *label; - GtkLabel *pid; - GtkImage *image; - GtkImage *check; -} SysprofProcessModelRowPrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofProcessModelRow, sysprof_process_model_row, GTK_TYPE_LIST_BOX_ROW) - -enum { - PROP_0, - PROP_ITEM, - PROP_SELECTED, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -GtkWidget * -sysprof_process_model_row_new (SysprofProcessModelItem *item) -{ - return g_object_new (SYSPROF_TYPE_PROCESS_MODEL_ROW, - "item", item, - NULL); -} - -SysprofProcessModelItem * -sysprof_process_model_row_get_item (SysprofProcessModelRow *self) -{ - SysprofProcessModelRowPrivate *priv = sysprof_process_model_row_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ROW (self), NULL); - - return priv->item; -} - -static void -sysprof_process_model_row_set_item (SysprofProcessModelRow *self, - SysprofProcessModelItem *item) -{ - SysprofProcessModelRowPrivate *priv = sysprof_process_model_row_get_instance_private (self); - - g_assert (SYSPROF_IS_PROCESS_MODEL_ROW (self)); - g_assert (SYSPROF_IS_PROCESS_MODEL_ITEM (item)); - - if (g_set_object (&priv->item, item)) - { - const gchar *command_line; - g_auto(GStrv) parts = NULL; - g_autofree gchar *pidstr = NULL; - const gchar * const *argv; - GPid pid; - - command_line = sysprof_process_model_item_get_command_line (item); - parts = g_strsplit (command_line ?: "", "\n", 0); - gtk_label_set_label (priv->label, parts [0]); - - if ((NULL != (argv = sysprof_process_model_item_get_argv (item))) && (argv[0] != NULL)) - { - g_autofree gchar *argvstr = g_strjoinv (" ", (gchar **)&argv[1]); - g_autofree gchar *escaped = g_markup_escape_text (argvstr, -1); - - gtk_label_set_label (priv->args_label, escaped); - } - - pid = sysprof_process_model_item_get_pid (item); - pidstr = g_strdup_printf ("%u", pid); - gtk_label_set_label (priv->pid, pidstr); - gtk_label_set_use_markup (priv->pid, TRUE); - } -} - -gboolean -sysprof_process_model_row_get_selected (SysprofProcessModelRow *self) -{ - SysprofProcessModelRowPrivate *priv = sysprof_process_model_row_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ROW (self), FALSE); - - return gtk_widget_get_visible (GTK_WIDGET (priv->check)); -} - -void -sysprof_process_model_row_set_selected (SysprofProcessModelRow *self, - gboolean selected) -{ - SysprofProcessModelRowPrivate *priv = sysprof_process_model_row_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_PROCESS_MODEL_ROW (self)); - - selected = !!selected; - - if (selected != sysprof_process_model_row_get_selected (self)) - { - gtk_widget_set_visible (GTK_WIDGET (priv->check), selected); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTED]); - } -} - -static gboolean -sysprof_process_model_row_query_tooltip (GtkWidget *widget, - gint x, - gint y, - gboolean keyboard_mode, - GtkTooltip *tooltip) -{ - SysprofProcessModelRow *self = (SysprofProcessModelRow *)widget; - SysprofProcessModelRowPrivate *priv = sysprof_process_model_row_get_instance_private (self); - - g_assert (SYSPROF_IS_PROCESS_MODEL_ROW (self)); - g_assert (GTK_IS_TOOLTIP (tooltip)); - - if (priv->item != NULL) - { - const gchar * const *argv = sysprof_process_model_item_get_argv (priv->item); - - if (argv != NULL) - { - g_autofree gchar *str = g_strjoinv (" ", (gchar **)argv); - gtk_tooltip_set_text (tooltip, str); - return TRUE; - } - } - - return FALSE; -} - -static void -sysprof_process_model_row_finalize (GObject *object) -{ - SysprofProcessModelRow *self = (SysprofProcessModelRow *)object; - SysprofProcessModelRowPrivate *priv = sysprof_process_model_row_get_instance_private (self); - - g_clear_object (&priv->item); - - G_OBJECT_CLASS (sysprof_process_model_row_parent_class)->finalize (object); -} - -static void -sysprof_process_model_row_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofProcessModelRow *self = SYSPROF_PROCESS_MODEL_ROW (object); - - switch (prop_id) - { - case PROP_ITEM: - g_value_set_object (value, sysprof_process_model_row_get_item (self)); - break; - - case PROP_SELECTED: - g_value_set_boolean (value, sysprof_process_model_row_get_selected (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_process_model_row_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofProcessModelRow *self = SYSPROF_PROCESS_MODEL_ROW (object); - - switch (prop_id) - { - case PROP_ITEM: - sysprof_process_model_row_set_item (self, g_value_get_object (value)); - break; - - case PROP_SELECTED: - sysprof_process_model_row_set_selected (self, g_value_get_boolean (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_process_model_row_class_init (SysprofProcessModelRowClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->finalize = sysprof_process_model_row_finalize; - object_class->get_property = sysprof_process_model_row_get_property; - object_class->set_property = sysprof_process_model_row_set_property; - - widget_class->query_tooltip = sysprof_process_model_row_query_tooltip; - - properties [PROP_ITEM] = - g_param_spec_object ("item", - "Item", - "Item", - SYSPROF_TYPE_PROCESS_MODEL_ITEM, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_SELECTED] = - g_param_spec_boolean ("selected", - "Selected", - "Selected", - FALSE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_template_from_resource (widget_class, - "/org/gnome/sysprof/ui/sysprof-process-model-row.ui"); - gtk_widget_class_bind_template_child_private (widget_class, SysprofProcessModelRow, args_label); - gtk_widget_class_bind_template_child_private (widget_class, SysprofProcessModelRow, image); - gtk_widget_class_bind_template_child_private (widget_class, SysprofProcessModelRow, label); - gtk_widget_class_bind_template_child_private (widget_class, SysprofProcessModelRow, pid); - gtk_widget_class_bind_template_child_private (widget_class, SysprofProcessModelRow, check); -} - -static void -sysprof_process_model_row_init (SysprofProcessModelRow *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); - - gtk_widget_set_has_tooltip (GTK_WIDGET (self), TRUE); -} diff --git a/src/libsysprof-ui/sysprof-process-model-row.h b/src/libsysprof-ui/sysprof-process-model-row.h deleted file mode 100644 index 63100357..00000000 --- a/src/libsysprof-ui/sysprof-process-model-row.h +++ /dev/null @@ -1,54 +0,0 @@ -/* sysprof-process-model-row.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PROCESS_MODEL_ROW (sysprof_process_model_row_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofProcessModelRow, sysprof_process_model_row, SYSPROF, PROCESS_MODEL_ROW, GtkListBoxRow) - -struct _SysprofProcessModelRowClass -{ - GtkListBoxRowClass parent; - - gpointer padding[4]; -}; - -SYSPROF_AVAILABLE_IN_ALL -GtkWidget *sysprof_process_model_row_new (SysprofProcessModelItem *item); -SYSPROF_AVAILABLE_IN_ALL -SysprofProcessModelItem *sysprof_process_model_row_get_item (SysprofProcessModelRow *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_process_model_row_get_selected (SysprofProcessModelRow *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_process_model_row_set_selected (SysprofProcessModelRow *self, - gboolean selected); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-process-model-row.ui b/src/libsysprof-ui/sysprof-process-model-row.ui deleted file mode 100644 index ee23e296..00000000 --- a/src/libsysprof-ui/sysprof-process-model-row.ui +++ /dev/null @@ -1,52 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-procs-visualizer.c b/src/libsysprof-ui/sysprof-procs-visualizer.c deleted file mode 100644 index 4c09cb33..00000000 --- a/src/libsysprof-ui/sysprof-procs-visualizer.c +++ /dev/null @@ -1,296 +0,0 @@ -/* sysprof-procs-visualizer.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-procs-visualizer" - -#include "config.h" - -#include - -#include "pointcache.h" -#include "sysprof-procs-visualizer.h" - -typedef struct -{ - volatile gint ref_count; - guint n_procs; - guint max_n_procs; - gint64 begin_time; - gint64 end_time; - gint64 duration; - PointCache *cache; - SysprofCaptureCursor *cursor; -} Discovery; - -struct _SysprofProcsVisualizer -{ - SysprofVisualizer parent_instance; - Discovery *discovery; -}; - -G_DEFINE_TYPE (SysprofProcsVisualizer, sysprof_procs_visualizer, SYSPROF_TYPE_VISUALIZER) - -static void -discovery_unref (Discovery *d) -{ - if (g_atomic_int_dec_and_test (&d->ref_count)) - { - g_clear_pointer (&d->cache, point_cache_unref); - g_clear_pointer (&d->cursor, sysprof_capture_cursor_unref); - g_slice_free (Discovery, d); - } -} - -static Discovery * -discovery_ref (Discovery *d) -{ - g_atomic_int_inc (&d->ref_count); - return d; -} - -static bool -discover_max_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - Discovery *d = user_data; - - g_assert (frame != NULL); - g_assert (d != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_PROCESS) - d->n_procs++; - else if (frame->type == SYSPROF_CAPTURE_FRAME_EXIT) - d->n_procs--; - - if (d->n_procs > d->max_n_procs) - d->max_n_procs = d->n_procs; - - return TRUE; -} - -static bool -calc_points_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - Discovery *d = user_data; - gdouble x, y; - - g_assert (frame != NULL); - g_assert (d != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_PROCESS) - d->n_procs++; - else if (frame->type == SYSPROF_CAPTURE_FRAME_EXIT) - d->n_procs--; - - x = (frame->time - d->begin_time) / (gdouble)d->duration; - y = (d->n_procs / (gdouble)d->max_n_procs) * .85; - - point_cache_add_point_to_set (d->cache, 1, x, y); - - return TRUE; -} - -static void -discovery_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Discovery *d = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_PROCS_VISUALIZER (source_object)); - - sysprof_capture_cursor_foreach (d->cursor, discover_max_cb, d); - - d->n_procs = 0; - sysprof_capture_cursor_reset (d->cursor); - - sysprof_capture_cursor_foreach (d->cursor, calc_points_cb, d); - - g_task_return_pointer (task, - discovery_ref (d), - (GDestroyNotify) discovery_unref); -} - -static void -handle_data_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofProcsVisualizer *self = (SysprofProcsVisualizer *)object; - Discovery *d; - - g_assert (SYSPROF_IS_PROCS_VISUALIZER (self)); - g_assert (G_IS_TASK (result)); - - if ((d = g_task_propagate_pointer (G_TASK (result), NULL))) - { - g_clear_pointer (&self->discovery, discovery_unref); - self->discovery = g_steal_pointer (&d); - gtk_widget_queue_allocate (GTK_WIDGET (self)); - } -} - -static void -sysprof_procs_visualizer_set_reader (SysprofVisualizer *visualizer, - SysprofCaptureReader *reader) -{ - static const SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_PROCESS, - SYSPROF_CAPTURE_FRAME_EXIT, - }; - SysprofProcsVisualizer *self = (SysprofProcsVisualizer *)visualizer; - g_autoptr(GTask) task = NULL; - Discovery *d; - - g_assert (SYSPROF_IS_PROCS_VISUALIZER (self)); - g_assert (reader != NULL); - - d = g_slice_new0 (Discovery); - d->ref_count = 1; - d->cache = point_cache_new (); - d->begin_time = sysprof_capture_reader_get_start_time (reader); - d->end_time = sysprof_capture_reader_get_end_time (reader); - d->cursor = sysprof_capture_cursor_new (reader); - d->duration = d->end_time - d->begin_time; - - point_cache_add_set (d->cache, 1); - sysprof_capture_cursor_add_condition (d->cursor, - sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types)); - - task = g_task_new (self, NULL, handle_data_cb, NULL); - g_task_set_source_tag (task, sysprof_procs_visualizer_set_reader); - g_task_set_task_data (task, d, (GDestroyNotify) discovery_unref); - g_task_run_in_thread (task, discovery_worker); -} - -static void -sysprof_procs_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofProcsVisualizer *self = (SysprofProcsVisualizer *)widget; - g_autofree SysprofVisualizerAbsolutePoint *points = NULL; - GtkAllocation alloc; - GdkRGBA background; - GdkRGBA foreground; - const Point *fpoints; - guint n_fpoints = 0; - Discovery *d; - cairo_t *cr; - gdouble last_x = 0; - gdouble last_y = 0; - - g_assert (SYSPROF_IS_PROCS_VISUALIZER (self)); - g_assert (snapshot != NULL); - - gtk_widget_get_allocation (widget, &alloc); - - gdk_rgba_parse (&foreground, "#813d9c"); - background = foreground; - background.alpha *= .5; - - GTK_WIDGET_CLASS (sysprof_procs_visualizer_parent_class)->snapshot (widget, snapshot); - - if (!(d = self->discovery) || d->cache == NULL) - return; - - if (!(fpoints = point_cache_get_points (d->cache, 1, &n_fpoints))) - return; - - /* This is all going to need offscreen drawing instead of new cairo every frame */ - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height)); - points = g_new0 (SysprofVisualizerAbsolutePoint, n_fpoints); - - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (self), - (const SysprofVisualizerRelativePoint *)fpoints, - n_fpoints, - points, - n_fpoints); - - last_x = points[0].x; - last_y = points[0].y; - - cairo_move_to (cr, last_x, alloc.height); - cairo_line_to (cr, last_x, last_y); - - for (guint i = 1; i < n_fpoints; i++) - { - cairo_curve_to (cr, - last_x + ((points[i].x - last_x) / 2), - last_y, - last_x + ((points[i].x - last_x) / 2), - points[i].y, - points[i].x, - points[i].y); - - last_x = points[i].x; - last_y = points[i].y; - } - - cairo_line_to (cr, last_x, alloc.height); - cairo_close_path (cr); - - cairo_set_line_width (cr, 1.0); - - gdk_cairo_set_source_rgba (cr, &background); - cairo_fill_preserve (cr); - gdk_cairo_set_source_rgba (cr, &foreground); - cairo_stroke (cr); - - cairo_destroy (cr); -} - -static void -sysprof_procs_visualizer_finalize (GObject *object) -{ - SysprofProcsVisualizer *self = (SysprofProcsVisualizer *)object; - - g_clear_pointer (&self->discovery, discovery_unref); - - G_OBJECT_CLASS (sysprof_procs_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_procs_visualizer_class_init (SysprofProcsVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass); - - object_class->finalize = sysprof_procs_visualizer_finalize; - - widget_class->snapshot = sysprof_procs_visualizer_snapshot; - - visualizer_class->set_reader = sysprof_procs_visualizer_set_reader; -} - -static void -sysprof_procs_visualizer_init (SysprofProcsVisualizer *self) -{ -} - -SysprofVisualizer * -sysprof_procs_visualizer_new (void) -{ - return g_object_new (SYSPROF_TYPE_PROCS_VISUALIZER, NULL); -} diff --git a/src/libsysprof-ui/sysprof-profiler-assistant.c b/src/libsysprof-ui/sysprof-profiler-assistant.c deleted file mode 100644 index e6721e3b..00000000 --- a/src/libsysprof-ui/sysprof-profiler-assistant.c +++ /dev/null @@ -1,493 +0,0 @@ -/* sysprof-profiler-assistant.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-profiler-assistant" - -#include "config.h" - -#include -#include - -#include "sysprof-platform.h" - -#include "sysprof-aid-icon.h" -#include "sysprof-control-source.h" -#include "sysprof-environ-editor.h" -#include "sysprof-model-filter.h" -#include "sysprof-profiler-assistant.h" -#include "sysprof-process-model-row.h" -#include "sysprof-theme-manager.h" -#include "sysprof-ui-private.h" - -#include "sysprof-battery-aid.h" -#include "sysprof-callgraph-aid.h" -#include "sysprof-cpu-aid.h" -#include "sysprof-memory-aid.h" -#include "sysprof-memprof-aid.h" -#include "sysprof-netdev-aid.h" -#include "sysprof-proxy-aid.h" -#include "sysprof-rapl-aid.h" - -struct _SysprofProfilerAssistant -{ - GtkWidget parent_instance; - - SysprofProcessModel *process_model; - - /* Template Objects */ - GtkSwitch *allow_throttling; - GtkButton *record_button; - AdwEntryRow *command_line; - GtkSearchEntry *search_entry; - GtkListBox *process_list_box; - SysprofEnvironEditor *environ_editor; - GtkFlowBox *aid_flow_box; - GtkSwitch *whole_system_switch; - GtkSwitch *launch_switch; - GtkSwitch *inherit_switch; -}; - -enum { - START_RECORDING, - N_SIGNALS -}; - -static guint signals [N_SIGNALS]; - -G_DEFINE_TYPE (SysprofProfilerAssistant, sysprof_profiler_assistant, GTK_TYPE_WIDGET) - -/** - * sysprof_profiler_assistant_new: - * - * Create a new #SysprofProfilerAssistant. - * - * Returns: (transfer full): a newly created #SysprofProfilerAssistant - * - * Since: 3.34 - */ -GtkWidget * -sysprof_profiler_assistant_new (void) -{ - return g_object_new (SYSPROF_TYPE_PROFILER_ASSISTANT, NULL); -} - -static void -sysprof_profiler_assistant_aid_activated_cb (SysprofProfilerAssistant *self, - SysprofAidIcon *icon, - GtkFlowBox *flow_box) -{ - g_assert (SYSPROF_IS_PROFILER_ASSISTANT (self)); - g_assert (SYSPROF_IS_AID_ICON (icon)); - g_assert (GTK_IS_FLOW_BOX (flow_box)); - - sysprof_aid_icon_toggle (icon); -} - -static GtkWidget * -create_process_row_cb (gpointer item_, - gpointer user_data) -{ - SysprofProcessModelItem *item = item_; - - g_assert (SYSPROF_IS_PROCESS_MODEL_ITEM (item)); - - return sysprof_process_model_row_new (item); -} - -static void -sysprof_profiler_assistant_notify_active_cb (SysprofProfilerAssistant *self, - GParamSpec *pspec, - GtkSwitch *switch_) -{ - g_assert (SYSPROF_IS_PROFILER_ASSISTANT (self)); - g_assert (GTK_IS_SWITCH (switch_)); - - if (gtk_switch_get_active (switch_)) - return; - - if (self->process_model == NULL) - { - self->process_model = sysprof_process_model_new (); - gtk_list_box_bind_model (self->process_list_box, - G_LIST_MODEL (self->process_model), - create_process_row_cb, - NULL, NULL); - sysprof_process_model_reload (self->process_model); - } -} - -static void -sysprof_profiler_assistant_row_activated_cb (SysprofProfilerAssistant *self, - SysprofProcessModelRow *row, - GtkListBox *list_box) -{ - g_assert (SYSPROF_PROFILER_ASSISTANT (self)); - g_assert (SYSPROF_IS_PROCESS_MODEL_ROW (row)); - g_assert (GTK_IS_LIST_BOX (list_box)); - - sysprof_process_model_row_set_selected (row, - !sysprof_process_model_row_get_selected (row)); -} - -static void -sysprof_profiler_assistant_command_line_changed_cb (SysprofProfilerAssistant *self, - GtkEntry *entry) -{ - g_auto(GStrv) argv = NULL; - GtkStyleContext *style_context; - const gchar *text; - gint argc; - - g_assert (SYSPROF_IS_PROFILER_ASSISTANT (self)); - g_assert (ADW_IS_ENTRY_ROW (entry)); - - style_context = gtk_widget_get_style_context (GTK_WIDGET (entry)); - text = gtk_editable_get_text (GTK_EDITABLE (entry)); - - if (text == NULL || text[0] == 0 || g_shell_parse_argv (text, &argc, &argv, NULL)) - gtk_style_context_remove_class (style_context, "error"); - else - gtk_style_context_add_class (style_context, "error"); -} - -static void -sysprof_profiler_assistant_foreach_cb (GtkWidget *widget, - SysprofProfiler *profiler) -{ - g_assert (GTK_IS_WIDGET (widget)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - if (SYSPROF_IS_PROCESS_MODEL_ROW (widget) && - sysprof_process_model_row_get_selected (SYSPROF_PROCESS_MODEL_ROW (widget))) - { - SysprofProcessModelItem *item; - GPid pid; - - item = sysprof_process_model_row_get_item (SYSPROF_PROCESS_MODEL_ROW (widget)); - pid = sysprof_process_model_item_get_pid (item); - - sysprof_profiler_add_pid (profiler, pid); - } - else if (SYSPROF_IS_AID_ICON (widget)) - { - if (sysprof_aid_icon_is_selected (SYSPROF_AID_ICON (widget))) - { - SysprofAid *aid = sysprof_aid_icon_get_aid (SYSPROF_AID_ICON (widget)); - - sysprof_aid_prepare (aid, profiler); - } - } -} - -static void -sysprof_profiler_assistant_record_clicked_cb (SysprofProfilerAssistant *self, - GtkButton *button) -{ - g_autoptr(SysprofProfiler) profiler = NULL; - g_autoptr(SysprofCaptureWriter) writer = NULL; - g_autoptr(SysprofSource) symbols_source = NULL; -#ifdef __linux__ - g_autoptr(SysprofSource) proc_source = NULL; -#endif - gint fd; - - g_assert (SYSPROF_IS_PROFILER_ASSISTANT (self)); - g_assert (GTK_IS_BUTTON (button)); - - gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); - - /* Setup a writer immediately */ - if (-1 == (fd = sysprof_memfd_create ("[sysprof-capture]")) || - !(writer = sysprof_capture_writer_new_from_fd (fd, 0))) - { - if (fd != -1) - close (fd); - return; - } - - profiler = sysprof_local_profiler_new (); - sysprof_profiler_set_writer (profiler, writer); - - /* Add pids to profiler */ - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->process_list_box)); - child; - child = gtk_widget_get_next_sibling (child)) - sysprof_profiler_assistant_foreach_cb (child, profiler); - - /* Setup whole system profiling */ - sysprof_profiler_set_whole_system (profiler, gtk_switch_get_active (self->whole_system_switch)); - - if (gtk_switch_get_active (self->launch_switch)) - { - g_auto(GStrv) argv = NULL; - g_auto(GStrv) env = NULL; - SysprofEnviron *environ_; - const gchar *command; - gint argc; - - command = gtk_editable_get_text (GTK_EDITABLE (self->command_line)); - g_shell_parse_argv (command, &argc, &argv, NULL); - - sysprof_profiler_set_spawn (profiler, TRUE); - sysprof_profiler_set_spawn_argv (profiler, (const gchar * const *)argv); - - environ_ = sysprof_environ_editor_get_environ (self->environ_editor); - env = sysprof_environ_get_environ (environ_); - sysprof_profiler_set_spawn_env (profiler, (const gchar * const *)env); - - sysprof_profiler_set_spawn_inherit_environ (profiler, - gtk_switch_get_active (self->inherit_switch)); - } - -#ifdef __linux__ - /* Always add the proc source */ - proc_source = sysprof_proc_source_new (); - sysprof_profiler_add_source (profiler, proc_source); - - { - g_autoptr(SysprofSource) governor = sysprof_governor_source_new (); - sysprof_governor_source_set_disable_governor (SYSPROF_GOVERNOR_SOURCE (governor), - !gtk_switch_get_active (self->allow_throttling)); - sysprof_profiler_add_source (profiler, governor); - } -#endif - - /* Always add symbol decoder to save to file immediately */ - symbols_source = sysprof_symbols_source_new (); - sysprof_profiler_add_source (profiler, symbols_source); - - /* Now allow the aids to add their sources */ - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->aid_flow_box)); - child; - child = gtk_widget_get_next_sibling (child)) - sysprof_profiler_assistant_foreach_cb (child, profiler); - - g_signal_emit (self, signals [START_RECORDING], 0, profiler); -} - -static gboolean -filter_by_search_text (GObject *object, - gpointer user_data) -{ - SysprofProcessModelItem *item = SYSPROF_PROCESS_MODEL_ITEM (object); - const gchar *haystack; - const gchar * const *argv; - const gchar *text = user_data; - - haystack = sysprof_process_model_item_get_command_line (item); - - if (haystack) - { - if (strcasestr (haystack, text) != NULL) - return TRUE; - } - - argv = sysprof_process_model_item_get_argv (item); - - if (argv) - { - for (guint i = 0; argv[i]; i++) - { - if (strcasestr (argv[i], text) != NULL) - return TRUE; - } - } - - return FALSE; -} - -static void -sysprof_profiler_assistant_search_changed_cb (SysprofProfilerAssistant *self, - GtkEditable *search_entry) -{ - g_autoptr(SysprofModelFilter) filter = NULL; - const char *text; - - g_assert (SYSPROF_IS_PROFILER_ASSISTANT (self)); - g_assert (GTK_IS_EDITABLE (search_entry)); - - if (self->process_model == NULL) - return; - - sysprof_process_model_queue_reload (self->process_model); - - text = gtk_editable_get_text (GTK_EDITABLE (search_entry)); - - if (text[0] == 0) - { - gtk_list_box_bind_model (self->process_list_box, - G_LIST_MODEL (self->process_model), - create_process_row_cb, - NULL, NULL); - return; - } - - filter = sysprof_model_filter_new (G_LIST_MODEL (self->process_model)); - sysprof_model_filter_set_filter_func (filter, - filter_by_search_text, - g_strdup (text), - g_free); - gtk_list_box_bind_model (self->process_list_box, - G_LIST_MODEL (filter), - create_process_row_cb, - NULL, NULL); -} - -static void -sysprof_profiler_assistant_dispose (GObject *object) -{ - SysprofProfilerAssistant *self = (SysprofProfilerAssistant *)object; - GtkWidget *child; - - g_clear_object (&self->process_model); - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - gtk_widget_unparent (child); - - G_OBJECT_CLASS (sysprof_profiler_assistant_parent_class)->dispose (object); -} - -static void -sysprof_profiler_assistant_class_init (SysprofProfilerAssistantClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_profiler_assistant_dispose; - - /** - * SysprofProfilerAssistant::start-recording: - * @self: a #SysprofProfilerAssistant - * @profiler: a #SysprofProfiler - * - * This signal is emitted when a new profiling session should start. - */ - signals [START_RECORDING] = - g_signal_new ("start-recording", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, - g_cclosure_marshal_VOID__OBJECT, - G_TYPE_NONE, 1, SYSPROF_TYPE_PROFILER); - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-profiler-assistant.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, allow_throttling); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, aid_flow_box); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, command_line); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, environ_editor); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, process_list_box); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, record_button); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, whole_system_switch); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, launch_switch); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, inherit_switch); - gtk_widget_class_bind_template_child (widget_class, SysprofProfilerAssistant, search_entry); - - sysprof_theme_manager_register_resource (sysprof_theme_manager_get_default (), - NULL, - NULL, - "/org/gnome/sysprof/css/SysprofProfilerAssistant-shared.css"); - - g_type_ensure (SYSPROF_TYPE_AID_ICON); - g_type_ensure (SYSPROF_TYPE_BATTERY_AID); - g_type_ensure (SYSPROF_TYPE_CALLGRAPH_AID); - g_type_ensure (SYSPROF_TYPE_CONTROL_SOURCE); - g_type_ensure (SYSPROF_TYPE_CPU_AID); - g_type_ensure (SYSPROF_TYPE_DISKSTAT_SOURCE); - g_type_ensure (SYSPROF_TYPE_ENVIRON_EDITOR); - g_type_ensure (SYSPROF_TYPE_MEMORY_AID); - g_type_ensure (SYSPROF_TYPE_MEMPROF_AID); - g_type_ensure (SYSPROF_TYPE_NETDEV_AID); - g_type_ensure (SYSPROF_TYPE_PROXY_AID); - g_type_ensure (SYSPROF_TYPE_RAPL_AID); -} - -static void -sysprof_profiler_assistant_init (SysprofProfilerAssistant *self) -{ - g_autoptr(SysprofEnviron) environ_ = sysprof_environ_new (); - - gtk_widget_init_template (GTK_WIDGET (self)); - - g_signal_connect_object (self->record_button, - "clicked", - G_CALLBACK (sysprof_profiler_assistant_record_clicked_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->command_line, - "changed", - G_CALLBACK (sysprof_profiler_assistant_command_line_changed_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->process_list_box, - "row-activated", - G_CALLBACK (sysprof_profiler_assistant_row_activated_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->whole_system_switch, - "notify::active", - G_CALLBACK (sysprof_profiler_assistant_notify_active_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->aid_flow_box, - "child-activated", - G_CALLBACK (sysprof_profiler_assistant_aid_activated_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->search_entry, - "changed", - G_CALLBACK (sysprof_profiler_assistant_search_changed_cb), - self, - G_CONNECT_SWAPPED); - - sysprof_environ_editor_set_environ (self->environ_editor, environ_); -} - -void -_sysprof_profiler_assistant_focus_record (SysprofProfilerAssistant *self) -{ - g_return_if_fail (SYSPROF_IS_PROFILER_ASSISTANT (self)); - - gtk_widget_grab_focus (GTK_WIDGET (self->record_button)); -} - -void -sysprof_profiler_assistant_set_executable (SysprofProfilerAssistant *self, - const gchar *path) -{ - g_return_if_fail (SYSPROF_IS_PROFILER_ASSISTANT (self)); - - if (path == NULL || path[0] == 0) - { - gtk_editable_set_text (GTK_EDITABLE (self->command_line), ""); - gtk_switch_set_active (self->launch_switch, FALSE); - } - else - { - gtk_editable_set_text (GTK_EDITABLE (self->command_line), path); - gtk_switch_set_active (self->launch_switch, TRUE); - gtk_widget_grab_focus (GTK_WIDGET (self->command_line)); - } -} diff --git a/src/libsysprof-ui/sysprof-profiler-assistant.h b/src/libsysprof-ui/sysprof-profiler-assistant.h deleted file mode 100644 index ff06c78b..00000000 --- a/src/libsysprof-ui/sysprof-profiler-assistant.h +++ /dev/null @@ -1,37 +0,0 @@ -/* sysprof-profiler-assistant.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PROFILER_ASSISTANT (sysprof_profiler_assistant_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofProfilerAssistant, sysprof_profiler_assistant, SYSPROF, PROFILER_ASSISTANT, GtkWidget) - -GtkWidget *sysprof_profiler_assistant_new (void); -void sysprof_profiler_assistant_set_executable (SysprofProfilerAssistant *self, - const gchar *path); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-profiler-assistant.ui b/src/libsysprof-ui/sysprof-profiler-assistant.ui deleted file mode 100644 index 669c21ab..00000000 --- a/src/libsysprof-ui/sysprof-profiler-assistant.ui +++ /dev/null @@ -1,279 +0,0 @@ - - - - sysprof-cpu - - - sysprof-memory - - - sysprof-allocations - - - sysprof-calgraph - - - sysprof-networking - - - sysprof-rapl - - - /org/gnome/Sysprof3/Profiler - session - org.gnome.Shell - - GNOME Shell - sysprof-library - - - Speedtrack - sysprof-gtk - - - libsysprof-speedtrack-4.so - - - - - GJS - sysprof-cli - - - - - - Application - sysprof-trace-app - - - SYSPROF_TRACE_FD - - - - - sysprof-battery - - - Disk - sysprof-disk - - - - - - diff --git a/src/libsysprof-ui/sysprof-proxy-aid.c b/src/libsysprof-ui/sysprof-proxy-aid.c deleted file mode 100644 index 6fd05c0e..00000000 --- a/src/libsysprof-ui/sysprof-proxy-aid.c +++ /dev/null @@ -1,209 +0,0 @@ -/* sysprof-proxy-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-proxy-aid" - -#include "sysprof-proxy-aid.h" - -typedef struct -{ - GBusType bus_type; - gchar *bus_name; - gchar *object_path; -} SysprofProxyAidPrivate; - -enum { - PROP_0, - PROP_BUS_TYPE, - PROP_BUS_NAME, - PROP_OBJECT_PATH, - N_PROPS -}; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofProxyAid, sysprof_proxy_aid, SYSPROF_TYPE_AID) - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_proxy_aid_prepare (SysprofAid *aid, - SysprofProfiler *profiler) -{ - SysprofProxyAid *self = (SysprofProxyAid *)aid; - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - g_autoptr(SysprofSource) source = NULL; - - g_assert (SYSPROF_IS_PROXY_AID (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - source = sysprof_proxy_source_new (priv->bus_type, priv->bus_name, priv->object_path); - sysprof_profiler_add_source (profiler, source); -} - -static void -sysprof_proxy_aid_finalize (GObject *object) -{ - SysprofProxyAid *self = (SysprofProxyAid *)object; - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - - g_clear_pointer (&priv->bus_name, g_free); - g_clear_pointer (&priv->object_path, g_free); - - G_OBJECT_CLASS (sysprof_proxy_aid_parent_class)->finalize (object); -} - -static void -sysprof_proxy_aid_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofProxyAid *self = SYSPROF_PROXY_AID (object); - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - - switch (prop_id) - { - case PROP_BUS_NAME: - g_value_set_string (value, priv->bus_name); - break; - - case PROP_OBJECT_PATH: - g_value_set_string (value, priv->object_path); - break; - - case PROP_BUS_TYPE: - g_value_set_enum (value, priv->bus_type); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_proxy_aid_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofProxyAid *self = SYSPROF_PROXY_AID (object); - - switch (prop_id) - { - case PROP_BUS_NAME: - sysprof_proxy_aid_set_bus_name (self, g_value_get_string (value)); - break; - - case PROP_BUS_TYPE: - sysprof_proxy_aid_set_bus_type (self, g_value_get_enum (value)); - break; - - case PROP_OBJECT_PATH: - sysprof_proxy_aid_set_object_path (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_proxy_aid_class_init (SysprofProxyAidClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - object_class->finalize = sysprof_proxy_aid_finalize; - object_class->get_property = sysprof_proxy_aid_get_property; - object_class->set_property = sysprof_proxy_aid_set_property; - - aid_class->prepare = sysprof_proxy_aid_prepare; - - properties [PROP_OBJECT_PATH] = - g_param_spec_string ("object-path", NULL, NULL, - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_BUS_NAME] = - g_param_spec_string ("bus-name", NULL, NULL, - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_BUS_TYPE] = - g_param_spec_enum ("bus-type", NULL, NULL, - G_TYPE_BUS_TYPE, - G_BUS_TYPE_SESSION, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_proxy_aid_init (SysprofProxyAid *self) -{ - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - - priv->bus_type = G_BUS_TYPE_SESSION; - priv->object_path = g_strdup ("/org/gnome/Sysprof3/Profiler"); -} - -void -sysprof_proxy_aid_set_bus_type (SysprofProxyAid *self, - GBusType bus_type) -{ - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_PROXY_AID (self)); - g_return_if_fail (bus_type == G_BUS_TYPE_SESSION || bus_type == G_BUS_TYPE_SYSTEM); - - priv->bus_type = bus_type; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUS_TYPE]); -} - -void -sysprof_proxy_aid_set_bus_name (SysprofProxyAid *self, - const gchar *bus_name) -{ - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_PROXY_AID (self)); - - if (g_strcmp0 (bus_name, priv->bus_name) != 0) - { - g_free (priv->bus_name); - priv->bus_name = g_strdup (bus_name); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUS_NAME]); - } -} - -void -sysprof_proxy_aid_set_object_path (SysprofProxyAid *self, - const gchar *object_path) -{ - SysprofProxyAidPrivate *priv = sysprof_proxy_aid_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_PROXY_AID (self)); - - if (g_strcmp0 (object_path, priv->object_path) != 0) - { - g_free (priv->object_path); - priv->object_path = g_strdup (object_path); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_OBJECT_PATH]); - } -} diff --git a/src/libsysprof-ui/sysprof-proxy-aid.h b/src/libsysprof-ui/sysprof-proxy-aid.h deleted file mode 100644 index 218ba117..00000000 --- a/src/libsysprof-ui/sysprof-proxy-aid.h +++ /dev/null @@ -1,46 +0,0 @@ -/* sysprof-proxy-aid.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-aid.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PROXY_AID (sysprof_proxy_aid_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofProxyAid, sysprof_proxy_aid, SYSPROF, PROXY_AID, SysprofAid) - -struct _SysprofProxyAidClass -{ - SysprofAidClass parent_class; - - /*< private >*/ - gpointer _reserved[8]; -}; - -void sysprof_proxy_aid_set_bus_type (SysprofProxyAid *self, - GBusType bus_type); -void sysprof_proxy_aid_set_bus_name (SysprofProxyAid *self, - const gchar *bus_name); -void sysprof_proxy_aid_set_object_path (SysprofProxyAid *self, - const gchar *obj_path); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-rapl-aid.c b/src/libsysprof-ui/sysprof-rapl-aid.c deleted file mode 100644 index 9e2c88ee..00000000 --- a/src/libsysprof-ui/sysprof-rapl-aid.c +++ /dev/null @@ -1,253 +0,0 @@ -/* sysprof-rapl-aid.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-rapl-aid" - -#include "config.h" - -#include - -#include "sysprof-color-cycle.h" -#include "sysprof-rapl-aid.h" -#include "sysprof-line-visualizer.h" -#include "sysprof-proxy-aid.h" - -struct _SysprofRaplAid -{ - SysprofProxyAid parent_instance; -}; - -typedef struct -{ - SysprofCaptureCursor *cursor; - SysprofDisplay *display; - GArray *counters; -} Present; - -G_DEFINE_TYPE (SysprofRaplAid, sysprof_rapl_aid, SYSPROF_TYPE_PROXY_AID) - -static void -present_free (gpointer data) -{ - Present *p = data; - - g_clear_pointer (&p->cursor, sysprof_capture_cursor_unref); - g_clear_pointer (&p->counters, g_array_unref); - g_clear_object (&p->display); - g_slice_free (Present, p); -} - -/** - * sysprof_rapl_aid_new: - * - * Create a new #SysprofRaplAid. - * - * Returns: (transfer full): a newly created #SysprofRaplAid - * - * Since: 3.34 - */ -SysprofAid * -sysprof_rapl_aid_new (void) -{ - return g_object_new (SYSPROF_TYPE_RAPL_AID, NULL); -} - -static bool -collect_info (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - SysprofCaptureCounterDefine *def = (SysprofCaptureCounterDefine *)frame; - Present *p = user_data; - - g_assert (frame != NULL); - g_assert (p != NULL); - g_assert (p->counters != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF) - { - for (guint i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *counter = &def->counters[i]; - - if (g_str_has_prefix (counter->category, "RAPL")) - g_array_append_vals (p->counters, counter, 1); - } - } - - return TRUE; -} - -static void -sysprof_rapl_aid_present_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Present *present = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_RAPL_AID (source_object)); - g_assert (present != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - sysprof_capture_cursor_foreach (present->cursor, collect_info, present); - g_task_return_pointer (task, - g_steal_pointer (&present->counters), - (GDestroyNotify) g_array_unref); -} - -static void -sysprof_rapl_aid_present_async (SysprofAid *aid, - SysprofCaptureReader *reader, - SysprofDisplay *display, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - static const SysprofCaptureFrameType types[] = { SYSPROF_CAPTURE_FRAME_CTRDEF, }; - g_autoptr(SysprofCaptureCondition) condition = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GTask) task = NULL; - Present present; - - g_assert (SYSPROF_IS_RAPL_AID (aid)); - g_assert (reader != NULL); - g_assert (SYSPROF_IS_DISPLAY (display)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - condition = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, g_steal_pointer (&condition)); - - present.cursor = g_steal_pointer (&cursor); - present.display = g_object_ref (display); - present.counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - - task = g_task_new (aid, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_rapl_aid_present_async); - g_task_set_task_data (task, - g_slice_dup (Present, &present), - present_free); - g_task_run_in_thread (task, sysprof_rapl_aid_present_worker); -} - -static gboolean -sysprof_rapl_aid_present_finish (SysprofAid *aid, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) counters = NULL; - Present *present; - - g_assert (SYSPROF_IS_AID (aid)); - g_assert (G_IS_TASK (result)); - - present = g_task_get_task_data (G_TASK (result)); - - if ((counters = g_task_propagate_pointer (G_TASK (result), error)) && counters->len) - { - g_autoptr(SysprofColorCycle) cycle = sysprof_color_cycle_new (); - g_autoptr(GHashTable) cat_to_row = g_hash_table_new (g_str_hash, g_str_equal); - SysprofVisualizerGroup *energy; - SysprofVisualizer *all; - guint found = 0; - - energy = g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, - "can-focus", TRUE, - "priority", -300, - "title", _("Energy Usage"), - "visible", TRUE, - NULL); - - all = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", _("Energy Usage (All)"), - "height-request", 35, - "visible", TRUE, - "y-lower", 0.0, - "units", "Watts", - NULL); - sysprof_visualizer_group_insert (energy, SYSPROF_VISUALIZER (all), 0, FALSE); - - for (guint i = 0; i < counters->len; i++) - { - const SysprofCaptureCounter *ctr = &g_array_index (counters, SysprofCaptureCounter, i); - - /* The pseudo counters (core:-1 cpu:-1) have "RAPL" as the group */ - if (g_strcmp0 (ctr->category, "RAPL") == 0) - { - GdkRGBA rgba; - - sysprof_color_cycle_next (cycle, &rgba); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (all), ctr->id, &rgba); - found++; - } - else if (g_str_has_prefix (ctr->category, "RAPL ")) - { - SysprofVisualizer *row; - GdkRGBA rgba; - - row = g_hash_table_lookup (cat_to_row, ctr->category); - - if (row == NULL) - { - row = g_object_new (SYSPROF_TYPE_LINE_VISUALIZER, - "title", ctr->category, - "height-request", 20, - "visible", FALSE, - "y-lower", 0.0, - "units", "Watts", - NULL); - g_hash_table_insert (cat_to_row, (gchar *)ctr->category, row); - sysprof_visualizer_group_insert (energy, SYSPROF_VISUALIZER (row), -1, TRUE); - } - - sysprof_color_cycle_next (cycle, &rgba); - sysprof_line_visualizer_add_counter (SYSPROF_LINE_VISUALIZER (row), ctr->id, &rgba); - found++; - } - } - - if (found > 0) - sysprof_display_add_group (present->display, energy); - else - g_object_unref (g_object_ref_sink (energy)); - } - - return counters != NULL; -} - -static void -sysprof_rapl_aid_class_init (SysprofRaplAidClass *klass) -{ - SysprofAidClass *aid_class = SYSPROF_AID_CLASS (klass); - - aid_class->present_async = sysprof_rapl_aid_present_async; - aid_class->present_finish = sysprof_rapl_aid_present_finish; -} - -static void -sysprof_rapl_aid_init (SysprofRaplAid *self) -{ - sysprof_aid_set_display_name (SYSPROF_AID (self), _("Energy Usage")); - sysprof_aid_set_icon_name (SYSPROF_AID (self), "battery-low-charging-symbolic"); - sysprof_proxy_aid_set_object_path (SYSPROF_PROXY_AID (self), "/org/gnome/Sysprof3/RAPL"); - sysprof_proxy_aid_set_bus_type (SYSPROF_PROXY_AID (self), G_BUS_TYPE_SYSTEM); - sysprof_proxy_aid_set_bus_name (SYSPROF_PROXY_AID (self), "org.gnome.Sysprof3"); -} diff --git a/src/libsysprof-ui/sysprof-rapl-aid.h b/src/libsysprof-ui/sysprof-rapl-aid.h deleted file mode 100644 index 8721c5dd..00000000 --- a/src/libsysprof-ui/sysprof-rapl-aid.h +++ /dev/null @@ -1,33 +0,0 @@ -/* sysprof-rapl-aid.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-proxy-aid.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_RAPL_AID (sysprof_rapl_aid_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofRaplAid, sysprof_rapl_aid, SYSPROF, RAPL_AID, SysprofProxyAid) - -SysprofAid *sysprof_rapl_aid_new (void); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-recording-state-view.c b/src/libsysprof-ui/sysprof-recording-state-view.c deleted file mode 100644 index ea025a4b..00000000 --- a/src/libsysprof-ui/sysprof-recording-state-view.c +++ /dev/null @@ -1,202 +0,0 @@ -/* sysprof-recording-state-view.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-recording-state-view.h" -#include "sysprof-time-label.h" - -typedef struct -{ - SysprofProfiler *profiler; - SysprofTimeLabel *elapsed; - GtkLabel *samples; - gulong notify_elapsed_handler; -} SysprofRecordingStateViewPrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofRecordingStateView, sysprof_recording_state_view, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_PROFILER, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -GtkWidget * -sysprof_recording_state_view_new (void) -{ - return g_object_new (SYSPROF_TYPE_RECORDING_STATE_VIEW, NULL); -} - -static void -sysprof_recording_state_view_notify_elapsed (SysprofRecordingStateView *self, - GParamSpec *pspec, - SysprofProfiler *profiler) -{ - SysprofRecordingStateViewPrivate *priv = sysprof_recording_state_view_get_instance_private (self); - SysprofCaptureWriter *writer; - gint64 elapsed; - - g_assert (SYSPROF_IS_RECORDING_STATE_VIEW (self)); - g_assert (SYSPROF_IS_PROFILER (profiler)); - - if ((writer = sysprof_profiler_get_writer (profiler))) - { - SysprofCaptureStat st; - g_autofree gchar *samples = NULL; - gint64 count; - - sysprof_capture_writer_stat (writer, &st); - count = st.frame_count[SYSPROF_CAPTURE_FRAME_SAMPLE] + - st.frame_count[SYSPROF_CAPTURE_FRAME_MARK] + - st.frame_count[SYSPROF_CAPTURE_FRAME_CTRSET]; - - samples = g_strdup_printf ("%"G_GINT64_FORMAT, count); - gtk_label_set_label (priv->samples, samples); - } - - elapsed = (gint64)sysprof_profiler_get_elapsed (profiler); - sysprof_time_label_set_duration (priv->elapsed, elapsed); -} - -static void -sysprof_recording_state_view_dispose (GObject *object) -{ - SysprofRecordingStateView *self = (SysprofRecordingStateView *)object; - SysprofRecordingStateViewPrivate *priv = sysprof_recording_state_view_get_instance_private (self); - GtkWidget *child; - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - gtk_widget_unparent (child); - - if (priv->profiler != NULL) - { - g_clear_signal_handler (&priv->notify_elapsed_handler, priv->profiler); - g_clear_object (&priv->profiler); - } - - G_OBJECT_CLASS (sysprof_recording_state_view_parent_class)->dispose (object); -} - -static void -sysprof_recording_state_view_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofRecordingStateView *self = SYSPROF_RECORDING_STATE_VIEW (object); - SysprofRecordingStateViewPrivate *priv = sysprof_recording_state_view_get_instance_private (self); - - switch (prop_id) - { - case PROP_PROFILER: - g_value_set_object (value, priv->profiler); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_recording_state_view_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofRecordingStateView *self = SYSPROF_RECORDING_STATE_VIEW (object); - - switch (prop_id) - { - case PROP_PROFILER: - sysprof_recording_state_view_set_profiler (self, g_value_get_object (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_recording_state_view_class_init (SysprofRecordingStateViewClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_recording_state_view_dispose; - object_class->get_property = sysprof_recording_state_view_get_property; - object_class->set_property = sysprof_recording_state_view_set_property; - - properties [PROP_PROFILER] = - g_param_spec_object ("profiler", - "Profiler", - "Profiler", - SYSPROF_TYPE_PROFILER, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-recording-state-view.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_bind_template_child_private (widget_class, SysprofRecordingStateView, elapsed); - gtk_widget_class_bind_template_child_private (widget_class, SysprofRecordingStateView, samples); - - g_type_ensure (SYSPROF_TYPE_TIME_LABEL); -} - -static void -sysprof_recording_state_view_init (SysprofRecordingStateView *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); -} - -void -sysprof_recording_state_view_set_profiler (SysprofRecordingStateView *self, - SysprofProfiler *profiler) -{ - SysprofRecordingStateViewPrivate *priv = sysprof_recording_state_view_get_instance_private (self); - - g_assert (SYSPROF_IS_RECORDING_STATE_VIEW (self)); - g_assert (!profiler || SYSPROF_IS_PROFILER (profiler)); - - sysprof_time_label_set_duration (priv->elapsed, 0); - - if (profiler != priv->profiler) - { - if (priv->profiler != NULL) - { - g_signal_handler_disconnect (priv->profiler, priv->notify_elapsed_handler); - g_clear_object (&priv->profiler); - } - - if (profiler != NULL) - { - priv->profiler = g_object_ref (profiler); - priv->notify_elapsed_handler = - g_signal_connect_object (profiler, - "notify::elapsed", - G_CALLBACK (sysprof_recording_state_view_notify_elapsed), - self, - G_CONNECT_SWAPPED); - } - } -} diff --git a/src/libsysprof-ui/sysprof-recording-state-view.ui b/src/libsysprof-ui/sysprof-recording-state-view.ui deleted file mode 100644 index 1885f885..00000000 --- a/src/libsysprof-ui/sysprof-recording-state-view.ui +++ /dev/null @@ -1,83 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-scrollmap.c b/src/libsysprof-ui/sysprof-scrollmap.c deleted file mode 100644 index 6dbddbb9..00000000 --- a/src/libsysprof-ui/sysprof-scrollmap.c +++ /dev/null @@ -1,333 +0,0 @@ -/* sysprof-scrollmap.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-scrollmap" - -#include "config.h" - -#include "sysprof-scrollmap.h" - -#define BOX_SIZE 4 - -struct _SysprofScrollmap -{ - GtkWidget parent_instance; - - GtkWidget *scrollbar; - - gint64 begin_time; - gint64 end_time; - - GArray *timings; - GArray *buckets; - GCancellable *cancellable; - - gint most; -}; - -typedef struct -{ - gint64 begin_time; - gint64 end_time; - GArray *timings; - gint width; - gint height; -} Recalculate; - -G_DEFINE_TYPE (SysprofScrollmap, sysprof_scrollmap, GTK_TYPE_WIDGET) - -static void -recalculate_free (gpointer data) -{ - Recalculate *state = data; - - g_clear_pointer (&state->timings, g_array_unref); - g_slice_free (Recalculate, state); -} - -static void -sysprof_scrollmap_recalculate_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Recalculate *state = task_data; - g_autoptr(GArray) buckets = NULL; - gint64 duration; - gint n_buckets; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_SCROLLMAP (source_object)); - g_assert (state != NULL); - g_assert (state->timings != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - duration = state->end_time - state->begin_time; - n_buckets = MAX (10, state->width / (BOX_SIZE + 1)); - buckets = g_array_sized_new (FALSE, TRUE, sizeof (gint), n_buckets); - g_array_set_size (buckets, n_buckets); - - for (guint i = 0; i < state->timings->len; i++) - { - gint64 t = g_array_index (state->timings, gint64, i); - gint n; - - if (t < state->begin_time || t > state->end_time) - continue; - - n = MIN (n_buckets - 1, ((t - state->begin_time) / (gdouble)duration) * n_buckets); - - g_assert (n < n_buckets); - - g_array_index (buckets, gint, n)++; - } - - g_task_return_pointer (task, - g_steal_pointer (&buckets), - (GDestroyNotify) g_array_unref); -} - -static void -sysprof_scrollmap_recalculate_async (SysprofScrollmap *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - GtkAllocation alloc; - Recalculate state; - - g_assert (SYSPROF_IS_SCROLLMAP (self)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_scrollmap_recalculate_async); - - if (self->timings == NULL) - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_CANCELLED, - "The operation was cancelled"); - return; - } - - gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); - - state.begin_time = self->begin_time; - state.end_time = self->end_time; - state.width = alloc.width; - state.height = alloc.height; - state.timings = g_array_ref (self->timings); - - g_task_set_task_data (task, - g_slice_dup (Recalculate, &state), - recalculate_free); - g_task_run_in_thread (task, sysprof_scrollmap_recalculate_worker); -} - -static GArray * -sysprof_scrollmap_recalculate_finish (SysprofScrollmap *self, - GAsyncResult *result, - GError **error) -{ - g_assert (SYSPROF_IS_SCROLLMAP (self)); - g_assert (G_IS_TASK (result)); - - return g_task_propagate_pointer (G_TASK (result), error); -} - -static inline void -draw_boxes (const GtkAllocation *alloc, - GtkSnapshot *snapshot, - int x, - int n_boxes, - const GdkRGBA *color) -{ - int y = alloc->y + alloc->height - BOX_SIZE; - - for (int i = 0; i < n_boxes; i++) - { - gtk_snapshot_append_color (snapshot, color, &GRAPHENE_RECT_INIT (x, y, BOX_SIZE, -BOX_SIZE)); - y -= (BOX_SIZE + 1); - } -} - -static void -sysprof_scrollmap_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofScrollmap *self = (SysprofScrollmap *)widget; - GtkStyleContext *style_context; - GtkAllocation alloc; - GdkRGBA color; - int max_boxes; - - g_assert (SYSPROF_IS_SCROLLMAP (self)); - g_assert (GTK_IS_SNAPSHOT (snapshot)); - - if (self->buckets == NULL) - goto chainup; - - gtk_widget_get_allocation (widget, &alloc); - - alloc.y += 3; - alloc.height -= 6; - - max_boxes = alloc.height / (BOX_SIZE + 1) - 1; - - style_context = gtk_widget_get_style_context (widget); - gtk_style_context_get_color (style_context, &color); - - for (guint i = 0; i < self->buckets->len; i++) - { - int n = g_array_index (self->buckets, gint, i); - int x = 1 + i * (BOX_SIZE + 1); - int b = max_boxes * (n / (gdouble)self->most); - - if (n > 0) - b = MAX (b, 1); - - draw_boxes (&alloc, snapshot, x, b, &color); - } - -chainup: - GTK_WIDGET_CLASS (sysprof_scrollmap_parent_class)->snapshot (widget, snapshot); -} - -static void -sysprof_scrollmap_recalculate_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofScrollmap *self = (SysprofScrollmap *)object; - g_autoptr(GArray) buckets = NULL; - - g_assert (SYSPROF_IS_SCROLLMAP (self)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (user_data == NULL); - - if ((buckets = sysprof_scrollmap_recalculate_finish (self, result, NULL))) - { - self->most = 0; - - for (guint i = 0; i < buckets->len; i++) - { - gint n = g_array_index (buckets, gint, i); - self->most = MAX (self->most, n); - } - - g_clear_pointer (&self->buckets, g_array_unref); - self->buckets = g_steal_pointer (&buckets); - - gtk_widget_queue_draw (GTK_WIDGET (self)); - } -} - -static void -sysprof_scrollmap_dispose (GObject *object) -{ - SysprofScrollmap *self = (SysprofScrollmap *)object; - - if (self->scrollbar) - { - gtk_widget_unparent (GTK_WIDGET (self->scrollbar)); - self->scrollbar = NULL; - } - - g_clear_pointer (&self->buckets, g_array_unref); - g_clear_pointer (&self->timings, g_array_unref); - - G_OBJECT_CLASS (sysprof_scrollmap_parent_class)->dispose (object); -} - -static void -sysprof_scrollmap_class_init (SysprofScrollmapClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_scrollmap_dispose; - - widget_class->snapshot = sysprof_scrollmap_snapshot; - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_set_css_name (widget_class, "scrollmap"); -} - -static void -sysprof_scrollmap_init (SysprofScrollmap *self) -{ - self->scrollbar = g_object_new (GTK_TYPE_SCROLLBAR, - "orientation", GTK_ORIENTATION_HORIZONTAL, - NULL); - gtk_widget_set_parent (GTK_WIDGET (self->scrollbar), GTK_WIDGET (self)); -} - -void -sysprof_scrollmap_set_timings (SysprofScrollmap *self, - GArray *timings) -{ - g_return_if_fail (SYSPROF_IS_SCROLLMAP (self)); - - if (timings != self->timings) - { - g_clear_pointer (&self->timings, g_array_unref); - self->timings = timings ? g_array_ref (timings) : NULL; - } -} - -void -sysprof_scrollmap_set_time_range (SysprofScrollmap *self, - gint64 begin_time, - gint64 end_time) -{ - g_return_if_fail (SYSPROF_IS_SCROLLMAP (self)); - - self->begin_time = begin_time; - self->end_time = end_time; - - g_cancellable_cancel (self->cancellable); - g_clear_object (&self->cancellable); - self->cancellable = g_cancellable_new (); - - sysprof_scrollmap_recalculate_async (self, - self->cancellable, - sysprof_scrollmap_recalculate_cb, - NULL); -} - -void -sysprof_scrollmap_set_adjustment (SysprofScrollmap *self, - GtkAdjustment *adjustment) -{ - g_return_if_fail (SYSPROF_IS_SCROLLMAP (self)); - g_return_if_fail (!adjustment || GTK_IS_ADJUSTMENT (adjustment)); - - gtk_scrollbar_set_adjustment (GTK_SCROLLBAR (self->scrollbar), adjustment); -} - -GtkAdjustment * -sysprof_scrollmap_get_adjustment (SysprofScrollmap *self) -{ - g_return_val_if_fail (SYSPROF_IS_SCROLLMAP (self), NULL); - - return gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (self->scrollbar)); -} diff --git a/src/libsysprof-ui/sysprof-scrollmap.h b/src/libsysprof-ui/sysprof-scrollmap.h deleted file mode 100644 index 2fbe1721..00000000 --- a/src/libsysprof-ui/sysprof-scrollmap.h +++ /dev/null @@ -1,40 +0,0 @@ -/* sysprof-scrollmap.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_SCROLLMAP (sysprof_scrollmap_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofScrollmap, sysprof_scrollmap, SYSPROF, SCROLLMAP, GtkWidget) - -GtkAdjustment *sysprof_scrollmap_get_adjustment (SysprofScrollmap *self); -void sysprof_scrollmap_set_adjustment (SysprofScrollmap *self, - GtkAdjustment *adjustment); -void sysprof_scrollmap_set_timings (SysprofScrollmap *self, - GArray *timings); -void sysprof_scrollmap_set_time_range (SysprofScrollmap *self, - gint64 begin_time, - gint64 end_time); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-tab.c b/src/libsysprof-ui/sysprof-tab.c deleted file mode 100644 index 8554cc71..00000000 --- a/src/libsysprof-ui/sysprof-tab.c +++ /dev/null @@ -1,158 +0,0 @@ -/* sysprof-tab.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-tab" - -#include "config.h" - -#include "sysprof-display-private.h" -#include "sysprof-tab.h" -#include "sysprof-ui-private.h" - -struct _SysprofTab -{ - GtkWidget parent_instance; - - GtkWidget *center_box; - GtkButton *close_button; - GtkLabel *title; - GtkImage *recording; - - SysprofDisplay *display; -}; - -G_DEFINE_TYPE (SysprofTab, sysprof_tab, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_DISPLAY, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -GtkWidget * -sysprof_tab_new (SysprofDisplay *display) -{ - return g_object_new (SYSPROF_TYPE_TAB, - "display", display, - NULL); -} - -static void -sysprof_tab_close_clicked (SysprofTab *self, - GtkButton *button) -{ - g_assert (SYSPROF_IS_TAB (self)); - g_assert (GTK_IS_BUTTON (button)); - - if (self->display) - _sysprof_display_destroy (self->display); -} - -static void -sysprof_tab_dispose (GObject *object) -{ - SysprofTab *self = (SysprofTab *)object; - - g_clear_pointer (&self->center_box, gtk_widget_unparent); - g_clear_weak_pointer (&self->display); - - G_OBJECT_CLASS (sysprof_tab_parent_class)->dispose (object); -} - -static void -sysprof_tab_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofTab *self = SYSPROF_TAB (object); - - switch (prop_id) - { - case PROP_DISPLAY: - g_value_set_object (value, self->display); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_tab_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofTab *self = SYSPROF_TAB (object); - - switch (prop_id) - { - case PROP_DISPLAY: - g_set_weak_pointer (&self->display, g_value_get_object (value)); - g_object_bind_property (self->display, "title", self->title, "label", G_BINDING_SYNC_CREATE); - g_object_bind_property (self->display, "recording", self->recording, "visible", G_BINDING_SYNC_CREATE); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_tab_class_init (SysprofTabClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_tab_dispose; - object_class->get_property = sysprof_tab_get_property; - object_class->set_property = sysprof_tab_set_property; - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-tab.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_bind_template_child (widget_class, SysprofTab, center_box); - gtk_widget_class_bind_template_child (widget_class, SysprofTab, close_button); - gtk_widget_class_bind_template_child (widget_class, SysprofTab, recording); - gtk_widget_class_bind_template_child (widget_class, SysprofTab, title); - - properties [PROP_DISPLAY] = - g_param_spec_object ("display", - "Display", - "The display widget for the tab", - SYSPROF_TYPE_DISPLAY, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_tab_init (SysprofTab *self) -{ - gtk_widget_init_template (GTK_WIDGET (self)); - - g_signal_connect_object (self->close_button, - "clicked", - G_CALLBACK (sysprof_tab_close_clicked), - self, - G_CONNECT_SWAPPED); -} diff --git a/src/libsysprof-ui/sysprof-tab.ui b/src/libsysprof-ui/sysprof-tab.ui deleted file mode 100644 index f57f717d..00000000 --- a/src/libsysprof-ui/sysprof-tab.ui +++ /dev/null @@ -1,36 +0,0 @@ - - - - diff --git a/src/libsysprof-ui/sysprof-theme-manager.c b/src/libsysprof-ui/sysprof-theme-manager.c deleted file mode 100644 index 3c07b27d..00000000 --- a/src/libsysprof-ui/sysprof-theme-manager.c +++ /dev/null @@ -1,269 +0,0 @@ -/* sysprof-theme-manager.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-theme-manager" - -#include "config.h" - -#include "sysprof-theme-manager.h" - -struct _SysprofThemeManager -{ - GObject parent_instance; - GHashTable *theme_resources; - guint reload_source; - guint registered_signals : 1; -}; - -typedef struct -{ - guint id; - gchar *key; - gchar *theme_name; - gchar *variant; - gchar *resource; - GtkCssProvider *provider; -} ThemeResource; - -G_DEFINE_TYPE (SysprofThemeManager, sysprof_theme_manager, G_TYPE_OBJECT) - -static void -theme_resource_free (gpointer data) -{ - ThemeResource *theme_resource = data; - - if (theme_resource != NULL) - { - g_clear_pointer (&theme_resource->key, g_free); - g_clear_pointer (&theme_resource->theme_name, g_free); - g_clear_pointer (&theme_resource->variant, g_free); - g_clear_pointer (&theme_resource->resource, g_free); - - if (theme_resource->provider != NULL) - { - gtk_style_context_remove_provider_for_display (gdk_display_get_default (), - GTK_STYLE_PROVIDER (theme_resource->provider)); - g_clear_object (&theme_resource->provider); - } - - g_slice_free (ThemeResource, theme_resource); - } -} - -static gboolean -theme_resource_matches (ThemeResource *theme_resource, - GtkSettings *settings) -{ - g_autofree gchar *theme_name = NULL; - gboolean dark_theme = FALSE; - - g_assert (theme_resource != NULL); - g_assert (GTK_IS_SETTINGS (settings)); - - if (theme_resource->theme_name == NULL) - return TRUE; - - g_object_get (settings, - "gtk-theme-name", &theme_name, - "gtk-application-prefer-dark-theme", &dark_theme, - NULL); - - if (g_strcmp0 (theme_name, theme_resource->theme_name) == 0) - { - if (dark_theme && g_strcmp0 ("dark", theme_resource->variant) == 0) - return TRUE; - - if (!dark_theme && (!theme_resource->variant || g_strcmp0 ("light", theme_resource->variant) == 0)) - return TRUE; - } - - return FALSE; -} - -static gboolean -sysprof_theme_manager_do_reload (gpointer data) -{ - SysprofThemeManager *self = data; - ThemeResource *theme_resource; - GHashTableIter iter; - GtkSettings *settings; - - g_assert (SYSPROF_IS_THEME_MANAGER (self)); - - self->reload_source = 0; - - settings = gtk_settings_get_default (); - - g_hash_table_iter_init (&iter, self->theme_resources); - - while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&theme_resource)) - { - if (theme_resource_matches (theme_resource, settings)) - { - if (theme_resource->provider == NULL) - { - theme_resource->provider = gtk_css_provider_new (); - gtk_css_provider_load_from_resource (theme_resource->provider, theme_resource->resource); - gtk_style_context_add_provider_for_display (gdk_display_get_default (), - GTK_STYLE_PROVIDER (theme_resource->provider), - GTK_STYLE_PROVIDER_PRIORITY_THEME+1); - } - } - else - { - if (theme_resource->provider != NULL) - { - gtk_style_context_remove_provider_for_display (gdk_display_get_default (), - GTK_STYLE_PROVIDER (theme_resource->provider)); - g_clear_object (&theme_resource->provider); - } - } - } - - return G_SOURCE_REMOVE; -} - -static void -sysprof_theme_manager_queue_reload (SysprofThemeManager *self) -{ - g_assert (SYSPROF_IS_THEME_MANAGER (self)); - - if (self->reload_source == 0) - self->reload_source = g_idle_add_full (G_PRIORITY_LOW, - sysprof_theme_manager_do_reload, - self, - NULL); -} - -static void -sysprof_theme_manager_finalize (GObject *object) -{ - SysprofThemeManager *self = (SysprofThemeManager *)object; - - if (self->reload_source != 0) - { - g_source_remove (self->reload_source); - self->reload_source = 0; - } - - g_clear_pointer (&self->theme_resources, g_hash_table_unref); - - G_OBJECT_CLASS (sysprof_theme_manager_parent_class)->finalize (object); -} - -static void -sysprof_theme_manager_class_init (SysprofThemeManagerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_theme_manager_finalize; -} - -static void -sysprof_theme_manager_init (SysprofThemeManager *self) -{ - self->theme_resources = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, theme_resource_free); - - gtk_icon_theme_add_resource_path (gtk_icon_theme_get_for_display (gdk_display_get_default ()), - "/org/gnome/sysprof/icons"); -} - -/** - * sysprof_theme_manager_get_default: - * - * Returns: (transfer none): An #SysprofThemeManager - */ -SysprofThemeManager * -sysprof_theme_manager_get_default (void) -{ - static SysprofThemeManager *instance; - - if (instance == NULL) - instance = g_object_new (SYSPROF_TYPE_THEME_MANAGER, NULL); - - return instance; -} - -guint -sysprof_theme_manager_register_resource (SysprofThemeManager *self, - const gchar *theme_name, - const gchar *variant, - const gchar *resource) -{ - ThemeResource *theme_resource; - static guint counter; - guint id; - - g_return_val_if_fail (SYSPROF_IS_THEME_MANAGER (self), 0); - - theme_resource = g_slice_new0 (ThemeResource); - theme_resource->id = id = ++counter; - theme_resource->key = g_strdup_printf ("%s-%s-%d", - theme_name ? theme_name : "shared", - variant ? variant : "light", - theme_resource->id); - theme_resource->theme_name = g_strdup (theme_name); - theme_resource->variant = g_strdup (variant); - theme_resource->resource = g_strdup (resource); - theme_resource->provider = NULL; - - g_hash_table_insert (self->theme_resources, theme_resource->key, theme_resource); - - if (!self->registered_signals) - { - self->registered_signals = TRUE; - g_signal_connect_object (gtk_settings_get_default (), - "notify::gtk-application-prefer-dark-theme", - G_CALLBACK (sysprof_theme_manager_queue_reload), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (gtk_settings_get_default (), - "notify::gtk-theme-name", - G_CALLBACK (sysprof_theme_manager_queue_reload), - self, - G_CONNECT_SWAPPED); - } - - sysprof_theme_manager_queue_reload (self); - - return id; -} - -void -sysprof_theme_manager_unregister (SysprofThemeManager *self, - guint registration_id) -{ - GHashTableIter iter; - ThemeResource *theme_resource; - - g_return_if_fail (SYSPROF_IS_THEME_MANAGER (self)); - - g_hash_table_iter_init (&iter, self->theme_resources); - - while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&theme_resource)) - { - if (theme_resource->id == registration_id) - { - /* Provider is unregistered during destroy */ - g_hash_table_iter_remove (&iter); - break; - } - } -} diff --git a/src/libsysprof-ui/sysprof-theme-manager.h b/src/libsysprof-ui/sysprof-theme-manager.h deleted file mode 100644 index 3724628a..00000000 --- a/src/libsysprof-ui/sysprof-theme-manager.h +++ /dev/null @@ -1,47 +0,0 @@ -/* sysprof-theme-manager.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_THEME_MANAGER (sysprof_theme_manager_get_type()) - -G_GNUC_INTERNAL -G_DECLARE_FINAL_TYPE (SysprofThemeManager, sysprof_theme_manager, SYSPROF, THEME_MANAGER, GObject) - -G_GNUC_INTERNAL -SysprofThemeManager *sysprof_theme_manager_get_default (void); -G_GNUC_INTERNAL -void sysprof_theme_manager_unregister (SysprofThemeManager *self, - guint registration_id); -G_GNUC_INTERNAL -guint sysprof_theme_manager_register_resource (SysprofThemeManager *self, - const gchar *theme_name, - const gchar *variant, - const gchar *resource); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-time-label.c b/src/libsysprof-ui/sysprof-time-label.c deleted file mode 100644 index 7d129124..00000000 --- a/src/libsysprof-ui/sysprof-time-label.c +++ /dev/null @@ -1,117 +0,0 @@ -/* sysprof-time-label.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-time-label" - -#include "config.h" - -#include "sysprof-time-label.h" - -struct _SysprofTimeLabel -{ - GtkWidget parent_instance; - GtkCenterBox *box; - GtkLabel *minutes; - GtkLabel *seconds; -}; - -G_DEFINE_TYPE (SysprofTimeLabel, sysprof_time_label, GTK_TYPE_WIDGET) - -static void -sysprof_time_label_dispose (GObject *object) -{ - SysprofTimeLabel *self = (SysprofTimeLabel *)object; - - if (self->box) - { - gtk_widget_unparent (GTK_WIDGET (self->box)); - self->box = NULL; - } - - G_OBJECT_CLASS (sysprof_time_label_parent_class)->dispose (object); -} - -static void -sysprof_time_label_class_init (SysprofTimeLabelClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = sysprof_time_label_dispose; - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); -} - -static void -sysprof_time_label_init (SysprofTimeLabel *self) -{ - PangoAttrList *attrs = pango_attr_list_new (); - GtkWidget *sep; - - pango_attr_list_insert (attrs, pango_attr_scale_new (4)); - pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); - - self->box = GTK_CENTER_BOX (gtk_center_box_new ()); - gtk_widget_set_parent (GTK_WIDGET (self->box), GTK_WIDGET (self)); - - self->minutes = g_object_new (GTK_TYPE_LABEL, - "attributes", attrs, - "xalign", 1.0f, - "hexpand", TRUE, - NULL); - gtk_center_box_set_start_widget (self->box, GTK_WIDGET (self->minutes)); - - sep = g_object_new (GTK_TYPE_LABEL, - "margin-start", 3, - "margin-end", 3, - "attributes", attrs, - "visible", TRUE, - "label", ":", - NULL); - gtk_center_box_set_center_widget (self->box, sep); - - self->seconds = g_object_new (GTK_TYPE_LABEL, - "attributes", attrs, - "visible", TRUE, - "xalign", 0.0f, - "hexpand", TRUE, - NULL); - gtk_center_box_set_end_widget (self->box, GTK_WIDGET (self->seconds)); -} - -void -sysprof_time_label_set_duration (SysprofTimeLabel *self, - guint duration) -{ - gchar minstr[12]; - gchar secstr[12]; - gint min, sec; - - g_return_if_fail (SYSPROF_IS_TIME_LABEL (self)); - - min = duration / 60; - sec = duration % 60; - - g_snprintf (minstr, sizeof minstr, "%02d", min); - g_snprintf (secstr, sizeof secstr, "%02d", sec); - - gtk_label_set_label (self->minutes, minstr); - gtk_label_set_label (self->seconds, secstr); -} diff --git a/src/libsysprof-ui/sysprof-time-visualizer.c b/src/libsysprof-ui/sysprof-time-visualizer.c deleted file mode 100644 index 58de9d35..00000000 --- a/src/libsysprof-ui/sysprof-time-visualizer.c +++ /dev/null @@ -1,543 +0,0 @@ -/* sysprof-time-visualizer.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-time-visualizer" - -#include "config.h" - -#include -#include -#include -#include - -#include "pointcache.h" -#include "sysprof-time-visualizer.h" - -typedef struct -{ - /* - * Our reader as assigned by the visualizer system. - */ - SysprofCaptureReader *reader; - - /* - * An array of LineInfo which contains information about the counters - * we need to render. - */ - GArray *lines; - - /* - * This is our set of cached points to render. Once it is assigned here, - * it is immutable (and therefore may be shared with worker processes - * that are rendering the points). - */ - PointCache *cache; - - /* - * If we have a new counter discovered or the reader is set, we might - * want to delay loading until we return to the main loop. This can - * help us avoid doing duplicate work. - */ - guint queued_load; -} SysprofTimeVisualizerPrivate; - -typedef struct -{ - guint id; - gdouble line_width; - GdkRGBA rgba; - guint use_default_style : 1; - guint use_dash : 1; -} LineInfo; - -typedef struct -{ - SysprofCaptureCursor *cursor; - GArray *lines; - PointCache *cache; - gint64 begin_time; - gint64 end_time; -} LoadData; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofTimeVisualizer, sysprof_time_visualizer, SYSPROF_TYPE_VISUALIZER) - -static void sysprof_time_visualizer_load_data_async (SysprofTimeVisualizer *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -static PointCache *sysprof_time_visualizer_load_data_finish (SysprofTimeVisualizer *self, - GAsyncResult *result, - GError **error); - -static gdouble dashes[] = { 1.0, 2.0 }; - -static void -load_data_free (gpointer data) -{ - LoadData *load = data; - - if (load != NULL) - { - g_clear_pointer (&load->lines, g_array_unref); - g_clear_pointer (&load->cursor, sysprof_capture_cursor_unref); - g_clear_pointer (&load->cache, point_cache_unref); - g_slice_free (LoadData, load); - } -} - -static GArray * -copy_array (GArray *ar) -{ - GArray *ret; - - ret = g_array_sized_new (FALSE, FALSE, g_array_get_element_size (ar), ar->len); - g_array_set_size (ret, ar->len); - memcpy (ret->data, ar->data, ar->len * g_array_get_element_size (ret)); - - return ret; -} - -static void -sysprof_time_visualizer_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofTimeVisualizer *self = (SysprofTimeVisualizer *)widget; - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - GtkStyleContext *style_context; - cairo_t *cr; - GtkAllocation alloc; - GdkRGBA foreground; - - g_assert (SYSPROF_IS_TIME_VISUALIZER (widget)); - g_assert (snapshot != NULL); - - gtk_widget_get_allocation (widget, &alloc); - - GTK_WIDGET_CLASS (sysprof_time_visualizer_parent_class)->snapshot (widget, snapshot); - - if (priv->cache == NULL) - return; - -#if 0 - if (!gdk_cairo_get_clip_rectangle (cr, &clip)) - return ret; -#else - alloc.x = 0; - alloc.y = 0; -#endif - - style_context = gtk_widget_get_style_context (widget); - gtk_style_context_get_color (style_context, &foreground); - - cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, 0, alloc.width, alloc.height)); - - gdk_cairo_set_source_rgba (cr, &foreground); - - for (guint line = 0; line < priv->lines->len; line++) - { - g_autofree SysprofVisualizerAbsolutePoint *points = NULL; - const LineInfo *line_info = &g_array_index (priv->lines, LineInfo, line); - const Point *fpoints; - guint n_fpoints = 0; - - fpoints = point_cache_get_points (priv->cache, line_info->id, &n_fpoints); - - if (n_fpoints > 0) - { - guint last_x = G_MAXUINT; - - points = g_new0 (SysprofVisualizerAbsolutePoint, n_fpoints); - - sysprof_visualizer_translate_points (SYSPROF_VISUALIZER (self), - (const SysprofVisualizerRelativePoint *)fpoints, - n_fpoints, - points, - n_fpoints); - - cairo_set_line_width (cr, 1.0); - - for (guint i = 0; i < n_fpoints; i++) - { - if ((guint)points[i].x != last_x) - last_x = (guint)points[i].x; - else - continue; - - cairo_move_to (cr, (guint)points[i].x + .5, alloc.height / 3); - cairo_line_to (cr, (guint)points[i].x + .5, alloc.height / 3 * 2); - } - - if (line_info->use_dash) - cairo_set_dash (cr, dashes, G_N_ELEMENTS (dashes), 0); - - cairo_stroke (cr); - } - } - - cairo_destroy (cr); -} - -static void -sysprof_time_visualizer_load_data_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofTimeVisualizer *self = (SysprofTimeVisualizer *)object; - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - g_autoptr(GError) error = NULL; - g_autoptr(PointCache) cache = NULL; - - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - - cache = sysprof_time_visualizer_load_data_finish (self, result, &error); - - if (cache == NULL) - { - g_warning ("%s", error->message); - return; - } - - g_clear_pointer (&priv->cache, point_cache_unref); - priv->cache = g_steal_pointer (&cache); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static gboolean -sysprof_time_visualizer_do_reload (gpointer data) -{ - SysprofTimeVisualizer *self = data; - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - - priv->queued_load = 0; - if (priv->reader != NULL) - sysprof_time_visualizer_load_data_async (self, - NULL, - sysprof_time_visualizer_load_data_cb, - NULL); - - return G_SOURCE_REMOVE; -} - -static void -sysprof_time_visualizer_queue_reload (SysprofTimeVisualizer *self) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - - if (priv->queued_load == 0) - priv->queued_load = - g_idle_add_full (G_PRIORITY_LOW, - sysprof_time_visualizer_do_reload, - self, - NULL); -} - -static void -sysprof_time_visualizer_set_reader (SysprofVisualizer *row, - SysprofCaptureReader *reader) -{ - SysprofTimeVisualizer *self = (SysprofTimeVisualizer *)row; - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - - if (priv->reader != reader) - { - if (priv->reader != NULL) - { - sysprof_capture_reader_unref (priv->reader); - priv->reader = NULL; - } - - if (reader != NULL) - priv->reader = sysprof_capture_reader_ref (reader); - - sysprof_time_visualizer_queue_reload (self); - } -} - -static void -sysprof_time_visualizer_finalize (GObject *object) -{ - SysprofTimeVisualizer *self = (SysprofTimeVisualizer *)object; - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_clear_pointer (&priv->lines, g_array_unref); - g_clear_pointer (&priv->cache, point_cache_unref); - g_clear_pointer (&priv->reader, sysprof_capture_reader_unref); - - g_clear_handle_id (&priv->queued_load, g_source_remove); - - G_OBJECT_CLASS (sysprof_time_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_time_visualizer_class_init (SysprofTimeVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - SysprofVisualizerClass *visualizer_class = SYSPROF_VISUALIZER_CLASS (klass); - - object_class->finalize = sysprof_time_visualizer_finalize; - - widget_class->snapshot = sysprof_time_visualizer_snapshot; - - visualizer_class->set_reader = sysprof_time_visualizer_set_reader; -} - -static void -sysprof_time_visualizer_init (SysprofTimeVisualizer *self) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - priv->lines = g_array_new (FALSE, FALSE, sizeof (LineInfo)); -} - -void -sysprof_time_visualizer_add_counter (SysprofTimeVisualizer *self, - guint counter_id, - const GdkRGBA *color) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - LineInfo line_info = {0}; - - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - g_assert (priv->lines != NULL); - - line_info.id = counter_id; - line_info.line_width = 1.0; - - if (color != NULL) - { - line_info.rgba = *color; - line_info.use_default_style = FALSE; - } - else - { - line_info.use_default_style = TRUE; - } - - g_array_append_val (priv->lines, line_info); - - if (SYSPROF_TIME_VISUALIZER_GET_CLASS (self)->counter_added) - SYSPROF_TIME_VISUALIZER_GET_CLASS (self)->counter_added (self, counter_id); - - sysprof_time_visualizer_queue_reload (self); -} - -void -sysprof_time_visualizer_clear (SysprofTimeVisualizer *self) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_TIME_VISUALIZER (self)); - - if (priv->lines->len > 0) - g_array_remove_range (priv->lines, 0, priv->lines->len); - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static inline gboolean -contains_id (GArray *ar, - guint id) -{ - for (guint i = 0; i < ar->len; i++) - { - const LineInfo *info = &g_array_index (ar, LineInfo, i); - - if (info->id == id) - return TRUE; - } - - return FALSE; -} - -static inline gdouble -calc_x (gint64 lower, - gint64 upper, - gint64 value) -{ - return (gdouble)(value - lower) / (gdouble)(upper - lower); -} - -static bool -sysprof_time_visualizer_load_data_frame_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - LoadData *load = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET || - frame->type == SYSPROF_CAPTURE_FRAME_CTRDEF); - g_assert (load != NULL); - - if (frame->type == SYSPROF_CAPTURE_FRAME_CTRSET) - { - const SysprofCaptureCounterSet *set = (SysprofCaptureCounterSet *)frame; - gdouble x = calc_x (load->begin_time, load->end_time, frame->time); - - for (guint i = 0; i < set->n_values; i++) - { - const SysprofCaptureCounterValues *group = &set->values[i]; - - for (guint j = 0; j < G_N_ELEMENTS (group->ids); j++) - { - guint counter_id = group->ids[j]; - - if (counter_id != 0 && contains_id (load->lines, counter_id)) - point_cache_add_point_to_set (load->cache, counter_id, x, 0); - } - } - } - - return TRUE; -} - -static void -sysprof_time_visualizer_load_data_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - LoadData *load = task_data; - g_autoptr(GArray) counter_ids = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_TIME_VISUALIZER (source_object)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - counter_ids = g_array_new (FALSE, FALSE, sizeof (guint)); - - for (guint i = 0; i < load->lines->len; i++) - { - const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i); - g_array_append_val (counter_ids, line_info->id); - } - - sysprof_capture_cursor_add_condition (load->cursor, - sysprof_capture_condition_new_where_counter_in (counter_ids->len, - (guint *)(gpointer)counter_ids->data)); - sysprof_capture_cursor_foreach (load->cursor, sysprof_time_visualizer_load_data_frame_cb, load); - g_task_return_pointer (task, g_steal_pointer (&load->cache), (GDestroyNotify)point_cache_unref); -} - -static void -sysprof_time_visualizer_load_data_async (SysprofTimeVisualizer *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - g_autoptr(GTask) task = NULL; - LoadData *load; - - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_priority (task, G_PRIORITY_LOW); - g_task_set_source_tag (task, sysprof_time_visualizer_load_data_async); - - if (priv->reader == NULL) - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "No data loaded"); - return; - } - - load = g_slice_new0 (LoadData); - load->cache = point_cache_new (); - load->begin_time = sysprof_capture_reader_get_start_time (priv->reader); - load->end_time = sysprof_capture_reader_get_end_time (priv->reader); - load->cursor = sysprof_capture_cursor_new (priv->reader); - load->lines = copy_array (priv->lines); - - for (guint i = 0; i < load->lines->len; i++) - { - const LineInfo *line_info = &g_array_index (load->lines, LineInfo, i); - - point_cache_add_set (load->cache, line_info->id); - } - - g_task_set_task_data (task, load, load_data_free); - g_task_run_in_thread (task, sysprof_time_visualizer_load_data_worker); -} - -static PointCache * -sysprof_time_visualizer_load_data_finish (SysprofTimeVisualizer *self, - GAsyncResult *result, - GError **error) -{ - g_assert (SYSPROF_IS_TIME_VISUALIZER (self)); - g_assert (G_IS_TASK (result)); - - return g_task_propagate_pointer (G_TASK (result), error); -} - -void -sysprof_time_visualizer_set_line_width (SysprofTimeVisualizer *self, - guint counter_id, - gdouble width) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_TIME_VISUALIZER (self)); - - for (guint i = 0; i < priv->lines->len; i++) - { - LineInfo *info = &g_array_index (priv->lines, LineInfo, i); - - if (info->id == counter_id) - { - info->line_width = width; - sysprof_time_visualizer_queue_reload (self); - break; - } - } -} - -void -sysprof_time_visualizer_set_dash (SysprofTimeVisualizer *self, - guint counter_id, - gboolean use_dash) -{ - SysprofTimeVisualizerPrivate *priv = sysprof_time_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_TIME_VISUALIZER (self)); - - for (guint i = 0; i < priv->lines->len; i++) - { - LineInfo *info = &g_array_index (priv->lines, LineInfo, i); - - if (info->id == counter_id) - { - info->use_dash = !!use_dash; - sysprof_time_visualizer_queue_reload (self); - break; - } - } -} diff --git a/src/libsysprof-ui/sysprof-time-visualizer.h b/src/libsysprof-ui/sysprof-time-visualizer.h deleted file mode 100644 index 1b9160ac..00000000 --- a/src/libsysprof-ui/sysprof-time-visualizer.h +++ /dev/null @@ -1,54 +0,0 @@ -/* sysprof-time-visualizer.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-visualizer.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_TIME_VISUALIZER (sysprof_time_visualizer_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofTimeVisualizer, sysprof_time_visualizer, SYSPROF, TIME_VISUALIZER, SysprofVisualizer) - -struct _SysprofTimeVisualizerClass -{ - SysprofVisualizerClass parent_class; - - void (*counter_added) (SysprofTimeVisualizer *self, - guint counter_id); - - /*< private >*/ - gpointer _reserved[16]; -}; - -GtkWidget *sysprof_time_visualizer_new (void); -void sysprof_time_visualizer_clear (SysprofTimeVisualizer *self); -void sysprof_time_visualizer_add_counter (SysprofTimeVisualizer *self, - guint counter_id, - const GdkRGBA *color); -void sysprof_time_visualizer_set_line_width (SysprofTimeVisualizer *self, - guint counter_id, - gdouble width); -void sysprof_time_visualizer_set_dash (SysprofTimeVisualizer *self, - guint counter_id, - gboolean use_dash); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-ui-private.h b/src/libsysprof-ui/sysprof-ui-private.h deleted file mode 100644 index 0216d3ba..00000000 --- a/src/libsysprof-ui/sysprof-ui-private.h +++ /dev/null @@ -1,48 +0,0 @@ -/* sysprof-ui-private.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-callgraph-page.h" -#include "sysprof-display.h" -#include "sysprof-profiler-assistant.h" - -G_BEGIN_DECLS - -void _sysprof_callgraph_page_set_failed (SysprofCallgraphPage *self); -void _sysprof_callgraph_page_set_loading (SysprofCallgraphPage *self, - gboolean loading); -void _sysprof_memory_page_set_failed (SysprofCallgraphPage *self); -void _sysprof_memory_page_set_loading (SysprofCallgraphPage *self, - gboolean loading); -void _sysprof_display_focus_record (SysprofDisplay *self); -void _sysprof_display_reload_page (SysprofDisplay *self, - SysprofPage *page); -void _sysprof_profiler_assistant_focus_record (SysprofProfilerAssistant *self); -gchar *_sysprof_format_duration (gint64 duration); - -#if !GLIB_CHECK_VERSION(2, 56, 0) -# define g_clear_weak_pointer(ptr) \ - (*(ptr) ? (g_object_remove_weak_pointer((GObject*)*(ptr), (gpointer*)ptr),*(ptr)=NULL,1) : 0) -# define g_set_weak_pointer(ptr,obj) \ - ((obj!=*(ptr))?(g_clear_weak_pointer(ptr),*(ptr)=obj,((obj)?g_object_add_weak_pointer((GObject*)obj,(gpointer*)ptr),NULL:NULL),1):0) -#endif - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-visualizer-group-header.c b/src/libsysprof-ui/sysprof-visualizer-group-header.c deleted file mode 100644 index 8dbadca6..00000000 --- a/src/libsysprof-ui/sysprof-visualizer-group-header.c +++ /dev/null @@ -1,241 +0,0 @@ -/* sysprof-visualizer-group-header.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-visualizer-group-header" - -#include "config.h" - -#include - -#include "sysprof-visualizer.h" -#include "sysprof-visualizer-group.h" -#include "sysprof-visualizer-group-header.h" -#include "sysprof-visualizer-group-private.h" - -struct _SysprofVisualizerGroupHeader -{ - GtkListBoxRow parent_instance; - - SysprofVisualizerGroup *group; - GtkBox *box; -}; - -G_DEFINE_TYPE (SysprofVisualizerGroupHeader, sysprof_visualizer_group_header, GTK_TYPE_LIST_BOX_ROW) - -enum { - PROP_0, - PROP_GROUP, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_visualizer_group_header_dispose (GObject *object) -{ - SysprofVisualizerGroupHeader *self = (SysprofVisualizerGroupHeader *)object; - - if (self->box) - { - gtk_widget_unparent (GTK_WIDGET (self->box)); - self->box = NULL; - } - - G_OBJECT_CLASS (sysprof_visualizer_group_header_parent_class)->dispose (object); -} - -static void -sysprof_visualizer_group_header_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizerGroupHeader *self = SYSPROF_VISUALIZER_GROUP_HEADER (object); - - switch (prop_id) - { - case PROP_GROUP: - g_value_set_object (value, _sysprof_visualizer_group_header_get_group (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizer_group_header_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizerGroupHeader *self = SYSPROF_VISUALIZER_GROUP_HEADER (object); - - switch (prop_id) - { - case PROP_GROUP: - self->group = g_value_get_object (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizer_group_header_class_init (SysprofVisualizerGroupHeaderClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_visualizer_group_header_dispose; - object_class->get_property = sysprof_visualizer_group_header_get_property; - object_class->set_property = sysprof_visualizer_group_header_set_property; - - properties [PROP_GROUP] = - g_param_spec_object ("group", - "Group", - "The group", - SYSPROF_TYPE_VISUALIZER_GROUP, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); -} - -static void -sysprof_visualizer_group_header_init (SysprofVisualizerGroupHeader *self) -{ - self->box = g_object_new (GTK_TYPE_BOX, - "orientation", GTK_ORIENTATION_VERTICAL, - "visible", TRUE, - NULL); - gtk_widget_set_parent (GTK_WIDGET (self->box), GTK_WIDGET (self)); -} - -void -_sysprof_visualizer_group_header_add_row (SysprofVisualizerGroupHeader *self, - guint position, - const gchar *title, - GMenuModel *menu, - GtkWidget *widget) -{ - GtkWidget *sibling; - GtkBox *box; - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP_HEADER (self)); - g_return_if_fail (SYSPROF_IS_VISUALIZER (widget)); - g_return_if_fail (!menu || G_IS_MENU_MODEL (menu)); - - box = g_object_new (GTK_TYPE_BOX, - "orientation", GTK_ORIENTATION_HORIZONTAL, - "spacing", 6, - "visible", TRUE, - NULL); - g_object_bind_property (widget, "visible", box, "visible", G_BINDING_SYNC_CREATE); - - sibling = gtk_widget_get_first_child (GTK_WIDGET (self->box)); - for (; position > 1 && sibling; position--) - sibling = gtk_widget_get_next_sibling (sibling); - gtk_box_insert_child_after (self->box, GTK_WIDGET (box), sibling); - - if (title != NULL) - { - g_autoptr(GtkSizeGroup) size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); - PangoAttrList *attrs = pango_attr_list_new (); - GtkLabel *label; - - pango_attr_list_insert (attrs, pango_attr_scale_new (0.83333)); - label = g_object_new (GTK_TYPE_LABEL, - "attributes", attrs, - "ellipsize", PANGO_ELLIPSIZE_MIDDLE, - "margin-top", 6, - "margin-bottom", 6, - "margin-start", 6, - "margin-end", 6, - "hexpand", TRUE, - "label", title, - "visible", TRUE, - "xalign", 0.0f, - NULL); - gtk_box_append (box, GTK_WIDGET (label)); - pango_attr_list_unref (attrs); - - gtk_size_group_add_widget (size_group, widget); - gtk_size_group_add_widget (size_group, GTK_WIDGET (box)); - } - - if (position == 0 && sysprof_visualizer_group_get_has_page (self->group)) - { - GtkImage *image; - - image = g_object_new (GTK_TYPE_IMAGE, - "icon-name", "view-paged-symbolic", - "tooltip-text", _("Select for more details"), - "pixel-size", 16, - "visible", TRUE, - NULL); - gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (image)), "dim-label"); - gtk_box_append (box, GTK_WIDGET (image)); - } - - if (menu != NULL) - { - GtkStyleContext *style_context; - GtkMenuButton *button; - - button = g_object_new (GTK_TYPE_MENU_BUTTON, - "child", g_object_new (GTK_TYPE_IMAGE, - "icon-name", "view-more-symbolic", - "visible", TRUE, - NULL), - "margin-end", 6, - "direction", GTK_ARROW_RIGHT, - "halign", GTK_ALIGN_CENTER, - "menu-model", menu, - "tooltip-text", _("Display supplemental graphs"), - "valign", GTK_ALIGN_CENTER, - "visible", TRUE, - NULL); - style_context = gtk_widget_get_style_context (GTK_WIDGET (button)); - gtk_style_context_add_class (style_context, "image-button"); - gtk_style_context_add_class (style_context, "small-button"); - gtk_style_context_add_class (style_context, "flat"); - - gtk_box_append (box, GTK_WIDGET (button)); - } -} - -SysprofVisualizerGroupHeader * -_sysprof_visualizer_group_header_new (SysprofVisualizerGroup *group) -{ - return g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP_HEADER, - "group", group, - NULL); -} - -SysprofVisualizerGroup * -_sysprof_visualizer_group_header_get_group (SysprofVisualizerGroupHeader *self) -{ - g_return_val_if_fail (SYSPROF_IS_VISUALIZER_GROUP_HEADER(self), NULL); - - return self->group; -} diff --git a/src/libsysprof-ui/sysprof-visualizer-group-private.h b/src/libsysprof-ui/sysprof-visualizer-group-private.h deleted file mode 100644 index a23b6127..00000000 --- a/src/libsysprof-ui/sysprof-visualizer-group-private.h +++ /dev/null @@ -1,45 +0,0 @@ -/* sysprof-visualizers-group-private.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-visualizer-group.h" -#include "sysprof-visualizer-group-header.h" - -G_BEGIN_DECLS - -void _sysprof_visualizer_group_set_reader (SysprofVisualizerGroup *self, - SysprofCaptureReader *reader); -SysprofVisualizerGroupHeader *_sysprof_visualizer_group_header_new (SysprofVisualizerGroup *group); -void _sysprof_visualizer_group_header_add_row (SysprofVisualizerGroupHeader *self, - guint position, - const gchar *title, - GMenuModel *menu, - GtkWidget *row); -void _sysprof_visualizer_group_header_remove_row (SysprofVisualizerGroupHeader *self, - guint row); -SysprofVisualizerGroup *_sysprof_visualizer_group_header_get_group (SysprofVisualizerGroupHeader *self); -void _sysprof_visualizer_group_set_header (SysprofVisualizerGroup *self, - SysprofVisualizerGroupHeader *header); - - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-visualizer-group.c b/src/libsysprof-ui/sysprof-visualizer-group.c deleted file mode 100644 index 2053e036..00000000 --- a/src/libsysprof-ui/sysprof-visualizer-group.c +++ /dev/null @@ -1,471 +0,0 @@ -/* sysprof-visualizer-group.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-visualizer-group" - -#include "config.h" - -#include - -#include "sysprof-visualizer.h" -#include "sysprof-visualizer-group.h" -#include "sysprof-visualizer-group-private.h" - -typedef struct -{ - /* Owned pointers */ - GMenuModel *menu; - GMenu *default_menu; - GMenu *rows_menu; - gchar *title; - GtkSizeGroup *size_group; - GSimpleActionGroup *actions; - - gint priority; - - guint has_page : 1; - - /* Weak pointers */ - SysprofVisualizerGroupHeader *header; - - /* Child Widgets */ - GtkBox *visualizers; -} SysprofVisualizerGroupPrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofVisualizerGroup, sysprof_visualizer_group, GTK_TYPE_LIST_BOX_ROW) - -enum { - PROP_0, - PROP_HAS_PAGE, - PROP_MENU, - PROP_PRIORITY, - PROP_TITLE, - N_PROPS -}; - -enum { - GROUP_ACTIVATED, - N_SIGNALS -}; - -static GParamSpec *properties [N_PROPS]; -static guint signals [N_SIGNALS]; - -/** - * sysprof_visualizer_group_new: - * - * Create a new #SysprofVisualizerGroup. - * - * Returns: (transfer full): a newly created #SysprofVisualizerGroup - */ -SysprofVisualizerGroup * -sysprof_visualizer_group_new (void) -{ - return g_object_new (SYSPROF_TYPE_VISUALIZER_GROUP, NULL); -} - -const gchar * -sysprof_visualizer_group_get_title (SysprofVisualizerGroup *self) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self), NULL); - - return priv->title; -} - -void -sysprof_visualizer_group_set_title (SysprofVisualizerGroup *self, - const gchar *title) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - - if (g_strcmp0 (priv->title, title) != 0) - { - g_free (priv->title); - priv->title = g_strdup (title); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); - } -} - -/** - * sysprof_visualizer_group_get_menu: - * - * Gets the menu for the group. - * - * Returns: (transfer none) (nullable): a #GMenuModel or %NULL - * - * Since: 3.34 - */ -GMenuModel * -sysprof_visualizer_group_get_menu (SysprofVisualizerGroup *self) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self), NULL); - - return priv->menu; -} - -void -sysprof_visualizer_group_set_menu (SysprofVisualizerGroup *self, - GMenuModel *menu) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - g_return_if_fail (!menu || G_IS_MENU_MODEL (menu)); - - if (g_set_object (&priv->menu, menu)) - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MENU]); -} - -static gchar * -create_action_name (const gchar *str) -{ - GString *ret = g_string_new (NULL); - - for (; *str; str = g_utf8_next_char (str)) - { - gunichar ch = g_utf8_get_char (str); - - if (g_unichar_isalnum (ch)) - g_string_append_unichar (ret, ch); - else - g_string_append_c (ret, '_'); - } - - return g_string_free (ret, FALSE); -} - -static void -sysprof_visualizer_group_finalize (GObject *object) -{ - SysprofVisualizerGroup *self = (SysprofVisualizerGroup *)object; - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_clear_pointer (&priv->title, g_free); - g_clear_object (&priv->menu); - g_clear_object (&priv->size_group); - g_clear_object (&priv->default_menu); - g_clear_object (&priv->rows_menu); - g_clear_object (&priv->actions); - - g_clear_weak_pointer (&priv->header); - - G_OBJECT_CLASS (sysprof_visualizer_group_parent_class)->finalize (object); -} - -static void -sysprof_visualizer_group_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizerGroup *self = SYSPROF_VISUALIZER_GROUP (object); - - switch (prop_id) - { - case PROP_HAS_PAGE: - g_value_set_boolean (value, sysprof_visualizer_group_get_has_page (self)); - break; - - case PROP_MENU: - g_value_set_object (value, sysprof_visualizer_group_get_menu (self)); - break; - - case PROP_PRIORITY: - g_value_set_int (value, sysprof_visualizer_group_get_priority (self)); - break; - - case PROP_TITLE: - g_value_set_string (value, sysprof_visualizer_group_get_title (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizer_group_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizerGroup *self = SYSPROF_VISUALIZER_GROUP (object); - - switch (prop_id) - { - case PROP_HAS_PAGE: - sysprof_visualizer_group_set_has_page (self, g_value_get_boolean (value)); - break; - - case PROP_MENU: - sysprof_visualizer_group_set_menu (self, g_value_get_object (value)); - break; - - case PROP_PRIORITY: - sysprof_visualizer_group_set_priority (self, g_value_get_int (value)); - break; - - case PROP_TITLE: - sysprof_visualizer_group_set_title (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizer_group_class_init (SysprofVisualizerGroupClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->finalize = sysprof_visualizer_group_finalize; - object_class->get_property = sysprof_visualizer_group_get_property; - object_class->set_property = sysprof_visualizer_group_set_property; - - properties [PROP_HAS_PAGE] = - g_param_spec_boolean ("has-page", - "Has Page", - "Has Page", - FALSE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_MENU] = - g_param_spec_object ("menu", - "Menu", - "Menu", - G_TYPE_MENU_MODEL, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_PRIORITY] = - g_param_spec_int ("priority", - "Priority", - "The Priority of the group, used for sorting", - G_MININT, G_MAXINT, 0, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_TITLE] = - g_param_spec_string ("title", - "Title", - "The title of the row", - NULL, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - signals [GROUP_ACTIVATED] = - g_signal_new ("group-activated", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - gtk_widget_class_set_css_name (widget_class, "SysprofVisualizerGroup"); -} - -static void -sysprof_visualizer_group_init (SysprofVisualizerGroup *self) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - g_autoptr(GMenuItem) item = NULL; - - priv->actions = g_simple_action_group_new (); - - priv->default_menu = g_menu_new (); - priv->rows_menu = g_menu_new (); - - item = g_menu_item_new_section (NULL, G_MENU_MODEL (priv->rows_menu)); - g_menu_append_item (priv->default_menu, item); - - priv->menu = g_object_ref (G_MENU_MODEL (priv->default_menu)); - - priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); - gtk_size_group_add_widget (priv->size_group, GTK_WIDGET (self)); - - priv->visualizers = g_object_new (GTK_TYPE_BOX, - "orientation", GTK_ORIENTATION_VERTICAL, - "visible", TRUE, - NULL); - gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (self), GTK_WIDGET (priv->visualizers)); -} - -void -_sysprof_visualizer_group_set_header (SysprofVisualizerGroup *self, - SysprofVisualizerGroupHeader *header) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - g_return_if_fail (!header || SYSPROF_IS_VISUALIZER_GROUP_HEADER (header)); - - if (g_set_weak_pointer (&priv->header, header)) - { - if (header != NULL) - { - guint position = 0; - - gtk_widget_insert_action_group (GTK_WIDGET (header), - "group", - G_ACTION_GROUP (priv->actions)); - gtk_size_group_add_widget (priv->size_group, GTK_WIDGET (header)); - - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (priv->visualizers)); - child; - child = gtk_widget_get_next_sibling (child)) - { - SysprofVisualizer *vis = SYSPROF_VISUALIZER (child); - const gchar *title; - GMenuModel *menu = NULL; - - g_assert (SYSPROF_IS_VISUALIZER (vis)); - - if (position == 0) - menu = priv->menu; - - title = sysprof_visualizer_get_title (vis); - - if (title == NULL) - title = priv->title; - - _sysprof_visualizer_group_header_add_row (header, - position, - title, - menu, - GTK_WIDGET (vis)); - - position++; - } - } - } -} - -void -_sysprof_visualizer_group_set_reader (SysprofVisualizerGroup *self, - SysprofCaptureReader *reader) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - g_return_if_fail (reader != NULL); - - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (priv->visualizers)); - child; - child = gtk_widget_get_next_sibling (child)) - sysprof_visualizer_set_reader (SYSPROF_VISUALIZER (child), reader); -} - -void -sysprof_visualizer_group_insert (SysprofVisualizerGroup *self, - SysprofVisualizer *visualizer, - gint position, - gboolean can_toggle) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - GtkWidget *sibling = NULL; - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - g_return_if_fail (SYSPROF_IS_VISUALIZER (visualizer)); - - if (position > 0) - { - sibling = gtk_widget_get_first_child (GTK_WIDGET (priv->visualizers)); - while (position > 1 && sibling) - { - sibling = gtk_widget_get_next_sibling (sibling); - position--; - } - } - gtk_box_insert_child_after (priv->visualizers, GTK_WIDGET (visualizer), sibling); - - if (can_toggle) - { - const gchar *title = sysprof_visualizer_get_title (visualizer); - g_autofree gchar *action_name = create_action_name (title); - g_autofree gchar *full_action_name = g_strdup_printf ("group.%s", action_name); - g_autoptr(GMenuItem) item = g_menu_item_new (title, full_action_name); - g_autoptr(GPropertyAction) action = NULL; - - action = g_property_action_new (action_name, visualizer, "visible"); - g_action_map_add_action (G_ACTION_MAP (priv->actions), G_ACTION (action)); - g_menu_item_set_attribute (item, "role", "s", "check"); - g_menu_append_item (priv->rows_menu, item); - } -} - -gint -sysprof_visualizer_group_get_priority (SysprofVisualizerGroup *self) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self), 0); - - return priv->priority; -} - -void -sysprof_visualizer_group_set_priority (SysprofVisualizerGroup *self, - gint priority) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - - if (priv->priority != priority) - { - priv->priority = priority; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PRIORITY]); - } -} - -gboolean -sysprof_visualizer_group_get_has_page (SysprofVisualizerGroup *self) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self), FALSE); - - return priv->has_page; -} - -void -sysprof_visualizer_group_set_has_page (SysprofVisualizerGroup *self, - gboolean has_page) -{ - SysprofVisualizerGroupPrivate *priv = sysprof_visualizer_group_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (self)); - - has_page = !!has_page; - - if (has_page != priv->has_page) - { - priv->has_page = has_page; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_PAGE]); - } -} diff --git a/src/libsysprof-ui/sysprof-visualizer-group.h b/src/libsysprof-ui/sysprof-visualizer-group.h deleted file mode 100644 index cac1c661..00000000 --- a/src/libsysprof-ui/sysprof-visualizer-group.h +++ /dev/null @@ -1,76 +0,0 @@ -/* sysprof-visualizer-group.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-visualizer.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_VISUALIZER_GROUP (sysprof_visualizer_group_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofVisualizerGroup, sysprof_visualizer_group, SYSPROF, VISUALIZER_GROUP, GtkListBoxRow) - -struct _SysprofVisualizerGroupClass -{ - GtkListBoxRowClass parent_class; - - void (*group_activated) (SysprofVisualizerGroup *self); - - /*< private >*/ - gpointer _reserved[16]; -}; - -SYSPROF_AVAILABLE_IN_ALL -SysprofVisualizerGroup *sysprof_visualizer_group_new (void); -SYSPROF_AVAILABLE_IN_ALL -GMenuModel *sysprof_visualizer_group_get_menu (SysprofVisualizerGroup *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_group_set_menu (SysprofVisualizerGroup *self, - GMenuModel *menu); -SYSPROF_AVAILABLE_IN_ALL -gint sysprof_visualizer_group_get_priority (SysprofVisualizerGroup *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_group_set_priority (SysprofVisualizerGroup *self, - gint priority); -SYSPROF_AVAILABLE_IN_ALL -const gchar *sysprof_visualizer_group_get_title (SysprofVisualizerGroup *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_group_set_title (SysprofVisualizerGroup *self, - const gchar *title); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_visualizer_group_get_has_page (SysprofVisualizerGroup *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_group_set_has_page (SysprofVisualizerGroup *self, - gboolean has_page); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_group_insert (SysprofVisualizerGroup *self, - SysprofVisualizer *visualizer, - gint position, - gboolean can_toggle); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-visualizer-ticks.c b/src/libsysprof-ui/sysprof-visualizer-ticks.c deleted file mode 100644 index 1f7049c8..00000000 --- a/src/libsysprof-ui/sysprof-visualizer-ticks.c +++ /dev/null @@ -1,324 +0,0 @@ -/* sysprof-visualizer-ticks.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-visualizer-ticks" - -#include "config.h" - -#include -#include - -#include "sysprof-visualizer-ticks.h" - -#define NSEC_PER_DAY (SYSPROF_NSEC_PER_SEC * 60L * 60L * 24L) -#define NSEC_PER_HOUR (SYSPROF_NSEC_PER_SEC * 60L * 60L) -#define NSEC_PER_MIN (SYSPROF_NSEC_PER_SEC * 60L) -#define NSEC_PER_MSEC (SYSPROF_NSEC_PER_SEC/G_GINT64_CONSTANT(1000)) -#define MIN_TICK_DISTANCE 20 -#define LABEL_HEIGHT_PX 10 - -struct _SysprofVisualizerTicks -{ - SysprofVisualizer parent_instance; -}; - -enum { - TICK_MINUTES, - TICK_HALF_MINUTES, - TICK_FIVE_SECONDS, - TICK_SECONDS, - TICK_HALF_SECONDS, - TICK_QUARTER_SECONDS, - TICK_TENTHS, - TICK_HUNDREDTHS, - TICK_THOUSANDTHS, - TICK_TEN_THOUSANDTHS, - N_TICKS -}; - -struct { - gint width; - gint height; - gint64 span; -} tick_sizing[N_TICKS] = { - { 1, 12, SYSPROF_NSEC_PER_SEC * 60 }, - { 1, 11, SYSPROF_NSEC_PER_SEC * 30 }, - { 1, 10, SYSPROF_NSEC_PER_SEC * 5 }, - { 1, 9, SYSPROF_NSEC_PER_SEC }, - { 1, 8, SYSPROF_NSEC_PER_SEC / 2 }, - { 1, 6, SYSPROF_NSEC_PER_SEC / 4 }, - { 1, 5, SYSPROF_NSEC_PER_SEC / 10 }, - { 1, 4, SYSPROF_NSEC_PER_SEC / 100 }, - { 1, 3, SYSPROF_NSEC_PER_SEC / 1000 }, - { 1, 1, SYSPROF_NSEC_PER_SEC / 10000 }, -}; - -G_DEFINE_TYPE (SysprofVisualizerTicks, sysprof_visualizer_ticks, SYSPROF_TYPE_VISUALIZER) - -static void -update_label_text (PangoLayout *layout, - gint64 time, - gboolean want_msec) -{ - g_autofree gchar *str = NULL; - gint64 tmp; - gint msec = 0; - gint hours = 0; - gint min = 0; - gint sec = 0; - G_GNUC_UNUSED gint days = 0; - - g_assert (PANGO_IS_LAYOUT (layout)); - - tmp = time % SYSPROF_NSEC_PER_SEC; - time -= tmp; - msec = tmp / 100000L; - - if (time >= NSEC_PER_DAY) - { - days = time / NSEC_PER_DAY; - time %= NSEC_PER_DAY; - } - - if (time >= NSEC_PER_HOUR) - { - hours = time / NSEC_PER_HOUR; - time %= NSEC_PER_HOUR; - } - - if (time >= NSEC_PER_MIN) - { - min = time / NSEC_PER_MIN; - time %= NSEC_PER_MIN; - } - - if (time >= SYSPROF_NSEC_PER_SEC) - { - sec = time / SYSPROF_NSEC_PER_SEC; - time %= SYSPROF_NSEC_PER_SEC; - } - - if (want_msec || (!hours && !min && !sec && msec)) - { - if (hours > 0) - str = g_strdup_printf ("%02u:%02u:%02u.%04u", hours, min, sec, msec); - else - str = g_strdup_printf ("%02u:%02u.%04u", min, sec, msec); - } - else - { - if (hours > 0) - str = g_strdup_printf ("%02u:%02u:%02u", hours, min, sec); - else - str = g_strdup_printf ("%02u:%02u", min, sec); - } - - pango_layout_set_text (layout, str, -1); -} - -static gboolean -draw_ticks (SysprofVisualizerTicks *self, - GtkSnapshot *snapshot, - GtkAllocation *area, - gint ticks, - gboolean label_mode, - const GdkRGBA *color) -{ - GtkAllocation alloc; - gint64 begin_time, end_time; - gdouble half; - gint count = 0; - - g_assert (SYSPROF_IS_VISUALIZER_TICKS (self)); - g_assert (snapshot != NULL); - g_assert (area != NULL); - g_assert (ticks >= 0); - g_assert (ticks < N_TICKS); - - begin_time = sysprof_visualizer_get_begin_time (SYSPROF_VISUALIZER (self)); - end_time = sysprof_visualizer_get_end_time (SYSPROF_VISUALIZER (self)); - - /* - * If we are in label_model, we don't draw the ticks but only the labels. - * That way we can determine which tick level managed to draw a tick in - * the visible region so we only show labels for the largest tick level. - * (Returning true from this method indicates we successfully drew a tick). - */ - - half = tick_sizing[ticks].width / 2.0; - - gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); - - if G_UNLIKELY (label_mode) - { - PangoFontDescription *font_desc; - PangoLayout *layout; - gboolean want_msec; - gint last_x2 = G_MININT; - gint w, h; - - layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), "00:10:00.0000"); - - font_desc = pango_font_description_new (); - pango_font_description_set_family_static (font_desc, "Monospace"); - pango_font_description_set_absolute_size (font_desc, LABEL_HEIGHT_PX * PANGO_SCALE); - pango_layout_set_font_description (layout, font_desc); - pango_font_description_free (font_desc); - - pango_layout_get_pixel_size (layout, &w, &h); - - /* If we are operating on smaller than seconds here, then we want - * to ensure we include msec with the timestamps. - */ - want_msec = tick_sizing[ticks].span < SYSPROF_NSEC_PER_SEC; - - for (gint64 t = begin_time; t <= end_time; t += tick_sizing[ticks].span) - { - gdouble x = sysprof_visualizer_get_x_for_time (SYSPROF_VISUALIZER (self), t); - - if (x < (last_x2 + MIN_TICK_DISTANCE)) - continue; - - update_label_text (layout, t - begin_time, want_msec); - pango_layout_get_pixel_size (layout, &w, &h); - - if (x + w <= alloc.width) - { - gtk_snapshot_save (snapshot); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT ((int)x + 2.5 - (int)half, 2)); - gtk_snapshot_append_layout (snapshot, layout, color); - gtk_snapshot_restore (snapshot); - } - - last_x2 = x + w; - } - - g_clear_object (&layout); - } - else - { - for (gint64 t = begin_time; t <= end_time; t += tick_sizing[ticks].span) - { - gdouble x = sysprof_visualizer_get_x_for_time (SYSPROF_VISUALIZER (self), t); - - gtk_snapshot_append_color (snapshot, color, - &GRAPHENE_RECT_INIT ((int)x - .5 - (int)half, - alloc.height, - (int)x - .5 - (int)half + tick_sizing[ticks].width, - alloc.height - tick_sizing[ticks].height)); - count++; - } - } - - return count > 2; -} - -static void -sysprof_visualizer_ticks_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofVisualizerTicks *self = SYSPROF_VISUALIZER_TICKS (widget); - GtkStyleContext *style; - GtkAllocation alloc; - gint64 timespan; - GdkRGBA color; - - g_assert (SYSPROF_IS_VISUALIZER_TICKS (self)); - g_assert (snapshot != NULL); - - timespan = sysprof_visualizer_get_duration (SYSPROF_VISUALIZER (self)); - if (timespan == 0) - return; - - gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); - alloc.x = 0; - alloc.y = 0; - - style = gtk_widget_get_style_context (widget); - gtk_style_context_get_color (style, &color); - - gtk_snapshot_render_background (snapshot, style, 0, 0, alloc.width, alloc.height); - - /* - * We need to discover up to what level we will draw tick marks. - * This is based on the width of the widget and the number of ticks - * to draw (which is determined from our timespan). We will skip a - * mark if they will end up less than MIN_TICK_DISTANCE px apart. - */ - - for (guint i = G_N_ELEMENTS (tick_sizing); i > 0; i--) - { - gint64 n_ticks = timespan / tick_sizing[i - 1].span; - gint largest_match = -1; - - if (n_ticks == 0 || (alloc.width / n_ticks) < MIN_TICK_DISTANCE) - continue; - - for (guint j = i; j > 0; j--) - { - if (draw_ticks (self, snapshot, &alloc, j - 1, FALSE, &color)) - largest_match = j - 1; - } - - if (largest_match != -1) - draw_ticks (self, snapshot, &alloc, largest_match, TRUE, &color); - - break; - } -} - -static void -sysprof_visualizer_ticks_measure (GtkWidget *widget, - GtkOrientation orientation, - int for_size, - int *minimum, - int *natural, - int *minimum_baseline, - int *natural_baseline) -{ - g_assert (SYSPROF_IS_VISUALIZER_TICKS (widget)); - - if (orientation == GTK_ORIENTATION_VERTICAL) - *minimum = *natural = tick_sizing[0].height + LABEL_HEIGHT_PX; - else - *minimum = *natural = 0; -} - -static void -sysprof_visualizer_ticks_class_init (SysprofVisualizerTicksClass *klass) -{ - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - widget_class->snapshot = sysprof_visualizer_ticks_snapshot; - widget_class->measure = sysprof_visualizer_ticks_measure; - - gtk_widget_class_set_css_name (widget_class, "SysprofVisualizerTicks"); -} - -static void -sysprof_visualizer_ticks_init (SysprofVisualizerTicks *self) -{ -} - -GtkWidget * -sysprof_visualizer_ticks_new (void) -{ - return g_object_new (SYSPROF_TYPE_VISUALIZER_TICKS, NULL); -} diff --git a/src/libsysprof-ui/sysprof-visualizer.c b/src/libsysprof-ui/sysprof-visualizer.c deleted file mode 100644 index 23cb75f0..00000000 --- a/src/libsysprof-ui/sysprof-visualizer.c +++ /dev/null @@ -1,289 +0,0 @@ -/* sysprof-visualizer.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-visualizer" - -#include "config.h" - -#include "sysprof-visualizer.h" - -typedef struct -{ - gchar *title; - - gint64 begin_time; - gint64 end_time; - gint64 duration; -} SysprofVisualizerPrivate; - -G_DEFINE_TYPE_WITH_PRIVATE (SysprofVisualizer, sysprof_visualizer, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_BEGIN_TIME, - PROP_END_TIME, - PROP_TITLE, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_visualizer_finalize (GObject *object) -{ - SysprofVisualizer *self = (SysprofVisualizer *)object; - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_clear_pointer (&priv->title, g_free); - - G_OBJECT_CLASS (sysprof_visualizer_parent_class)->finalize (object); -} - -static void -sysprof_visualizer_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizer *self = SYSPROF_VISUALIZER (object); - - switch (prop_id) - { - case PROP_TITLE: - g_value_set_string (value, sysprof_visualizer_get_title (self)); - break; - - case PROP_BEGIN_TIME: - g_value_set_int64 (value, sysprof_visualizer_get_begin_time (self)); - break; - - case PROP_END_TIME: - g_value_set_int64 (value, sysprof_visualizer_get_end_time (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizer_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizer *self = SYSPROF_VISUALIZER (object); - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - switch (prop_id) - { - case PROP_TITLE: - sysprof_visualizer_set_title (self, g_value_get_string (value)); - break; - - case PROP_BEGIN_TIME: - priv->begin_time = g_value_get_int64 (value); - priv->duration = priv->end_time - priv->begin_time; - break; - - case PROP_END_TIME: - priv->end_time = g_value_get_int64 (value); - priv->duration = priv->end_time - priv->begin_time; - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizer_class_init (SysprofVisualizerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->finalize = sysprof_visualizer_finalize; - object_class->get_property = sysprof_visualizer_get_property; - object_class->set_property = sysprof_visualizer_set_property; - - properties [PROP_BEGIN_TIME] = - g_param_spec_int64 ("begin-time", - "Begin Time", - "Begin Time", - G_MININT64, - G_MAXINT64, - 0, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_END_TIME] = - g_param_spec_int64 ("end-time", - "End Time", - "End Time", - G_MININT64, - G_MAXINT64, - 0, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_TITLE] = - g_param_spec_string ("title", - "Title", - "The title for the row", - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - gtk_widget_class_set_css_name (widget_class, "SysprofVisualizer"); -} - -static void -sysprof_visualizer_init (SysprofVisualizer *self) -{ -} - -const gchar * -sysprof_visualizer_get_title (SysprofVisualizer *self) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER (self), 0); - - return priv->title; -} - -void -sysprof_visualizer_set_title (SysprofVisualizer *self, - const gchar *title) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER (self)); - - if (g_strcmp0 (priv->title, title) != 0) - { - g_free (priv->title); - priv->title = g_strdup (title); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]); - } -} - -gint64 -sysprof_visualizer_get_begin_time (SysprofVisualizer *self) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER (self), 0); - - return priv->begin_time; -} - -gint64 -sysprof_visualizer_get_end_time (SysprofVisualizer *self) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_VISUALIZER (self), 0); - - return priv->end_time; -} - -gint64 -sysprof_visualizer_get_duration (SysprofVisualizer *self) -{ - g_return_val_if_fail (SYSPROF_IS_VISUALIZER (self), 0); - - return sysprof_visualizer_get_end_time (self) - - sysprof_visualizer_get_begin_time (self); -} - -void -sysprof_visualizer_set_reader (SysprofVisualizer *self, - SysprofCaptureReader *reader) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER (self)); - g_return_if_fail (reader != NULL); - - if (priv->begin_time == 0 || priv->end_time == 0) - { - priv->begin_time = sysprof_capture_reader_get_start_time (reader); - priv->end_time = sysprof_capture_reader_get_end_time (reader); - priv->duration = priv->end_time - priv->begin_time; - } - - if (SYSPROF_VISUALIZER_GET_CLASS (self)->set_reader) - SYSPROF_VISUALIZER_GET_CLASS (self)->set_reader (self, reader); - - gtk_widget_queue_allocate (GTK_WIDGET (self)); -} - -void -sysprof_visualizer_translate_points (SysprofVisualizer *self, - const SysprofVisualizerRelativePoint *in_points, - guint n_in_points, - SysprofVisualizerAbsolutePoint *out_points, - guint n_out_points) -{ - int width; - int height; - - g_return_if_fail (SYSPROF_IS_VISUALIZER (self)); - g_return_if_fail (in_points != NULL); - g_return_if_fail (out_points != NULL); - g_return_if_fail (n_in_points == n_out_points); - - width = gtk_widget_get_width (GTK_WIDGET (self)); - height = gtk_widget_get_height (GTK_WIDGET (self)); - - for (guint i = 0; i < n_in_points; i++) - { - out_points[i].x = (in_points[i].x * width); - out_points[i].y = height - (ABS (in_points[i].y) * height); - } -} - -gint -sysprof_visualizer_get_x_for_time (SysprofVisualizer *self, - gint64 time) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - return ((time - priv->begin_time) / (gdouble)priv->duration) * gtk_widget_get_width (GTK_WIDGET (self)); -} - -void -sysprof_visualizer_set_time_range (SysprofVisualizer *self, - gint64 begin_time, - gint64 end_time) -{ - SysprofVisualizerPrivate *priv = sysprof_visualizer_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_VISUALIZER (self)); - - priv->begin_time = begin_time; - priv->end_time = end_time; - priv->duration = end_time - begin_time; - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BEGIN_TIME]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_END_TIME]); - - gtk_widget_queue_allocate (GTK_WIDGET (self)); -} diff --git a/src/libsysprof-ui/sysprof-visualizer.h b/src/libsysprof-ui/sysprof-visualizer.h deleted file mode 100644 index 74b8a31c..00000000 --- a/src/libsysprof-ui/sysprof-visualizer.h +++ /dev/null @@ -1,88 +0,0 @@ -/* sysprof-visualizer.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_UI_INSIDE) && !defined (SYSPROF_UI_COMPILATION) -# error "Only can be included directly." -#endif - -#include -#include - -G_BEGIN_DECLS - -typedef struct -{ - gdouble x; - gdouble y; -} SysprofVisualizerRelativePoint; - -typedef struct -{ - gint x; - gint y; -} SysprofVisualizerAbsolutePoint; - -#define SYSPROF_TYPE_VISUALIZER (sysprof_visualizer_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofVisualizer, sysprof_visualizer, SYSPROF, VISUALIZER, GtkWidget) - -struct _SysprofVisualizerClass -{ - GtkWidgetClass parent_class; - - void (*set_reader) (SysprofVisualizer *self, - SysprofCaptureReader *reader); - - /*< private >*/ - gpointer _reserved[16]; -}; - -SYSPROF_AVAILABLE_IN_ALL -const gchar *sysprof_visualizer_get_title (SysprofVisualizer *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_set_title (SysprofVisualizer *self, - const gchar *title); -SYSPROF_AVAILABLE_IN_ALL -gint64 sysprof_visualizer_get_begin_time (SysprofVisualizer *self); -SYSPROF_AVAILABLE_IN_ALL -gint64 sysprof_visualizer_get_end_time (SysprofVisualizer *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_set_time_range (SysprofVisualizer *self, - gint64 begin_time, - gint64 end_time); -SYSPROF_AVAILABLE_IN_ALL -gint64 sysprof_visualizer_get_duration (SysprofVisualizer *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_set_reader (SysprofVisualizer *self, - SysprofCaptureReader *reader); -SYSPROF_AVAILABLE_IN_ALL -gint sysprof_visualizer_get_x_for_time (SysprofVisualizer *self, - gint64 time); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_visualizer_translate_points (SysprofVisualizer *self, - const SysprofVisualizerRelativePoint *in_points, - guint n_in_points, - SysprofVisualizerAbsolutePoint *out_points, - guint n_out_points); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-visualizers-frame.c b/src/libsysprof-ui/sysprof-visualizers-frame.c deleted file mode 100644 index 33af9ce3..00000000 --- a/src/libsysprof-ui/sysprof-visualizers-frame.c +++ /dev/null @@ -1,721 +0,0 @@ -/* sysprof-visualizers-frame.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-visualizers-frame" - -#include "config.h" - -#include "sysprof-scrollmap.h" -#include "sysprof-visualizer-group-private.h" -#include "sysprof-visualizer-ticks.h" -#include "sysprof-visualizers-frame.h" -#include "sysprof-zoom-manager.h" - -struct _SysprofVisualizersFrame -{ - GtkWidget parent_instance; - - /* Drag selection tracking */ - SysprofSelection *selection; - gint64 drag_begin_at; - gint64 drag_selection_at; - guint button_pressed : 1; - - /* Help avoid over-resizing/allocating */ - GtkAllocation last_alloc; - gdouble last_zoom; - - /* Known time range from the capture */ - gint64 begin_time; - gint64 end_time; - - /* Template Widgets */ - GtkListBox *groups; - GtkListBox *visualizers; - SysprofScrollmap *hscrollbar; - SysprofVisualizerTicks *ticks; - GtkScrolledWindow *ticks_scroller; - GtkScrolledWindow *hscroller; - GtkScrolledWindow *vscroller; - SysprofZoomManager *zoom_manager; - GtkScale *zoom_scale; - GtkSizeGroup *left_column; - GtkViewport *ticks_viewport; - GtkViewport *visualizers_viewport; -}; - -typedef struct -{ - GtkListBox *list; - GtkStyleContext *style_context; - GtkSnapshot *snapshot; - int width; - int height; - gint64 begin_time; - gint64 duration; -} SelectionDraw; - -G_DEFINE_TYPE (SysprofVisualizersFrame, sysprof_visualizers_frame, GTK_TYPE_WIDGET) - -enum { - PROP_0, - PROP_SELECTED_GROUP, - PROP_SELECTION, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static gint64 -get_time_from_x (SysprofVisualizersFrame *self, - gdouble x) -{ - GtkAllocation alloc; - gdouble ratio; - gint64 duration; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - - gtk_widget_get_allocation (GTK_WIDGET (self->ticks), &alloc); - duration = sysprof_visualizer_get_duration (SYSPROF_VISUALIZER (self->ticks)); - - if (alloc.width < 1) - return 0; - - ratio = x / alloc.width; - - return self->begin_time + (ratio * duration); -} - -static void -draw_selection_cb (SysprofSelection *selection, - gint64 range_begin, - gint64 range_end, - gpointer user_data) -{ - SelectionDraw *draw = user_data; - GdkRectangle area; - gdouble x, x2; - - g_assert (SYSPROF_IS_SELECTION (selection)); - g_assert (draw != NULL); - g_assert (draw->snapshot != NULL); - g_assert (GTK_IS_LIST_BOX (draw->list)); - - x = (range_begin - draw->begin_time) / (gdouble)draw->duration; - x2 = (range_end - draw->begin_time) / (gdouble)draw->duration; - - area.x = x * draw->width; - area.width = (x2 * draw->width) - area.x; - area.y = 0; - area.height = draw->height; - - if (area.width < 0) - { - area.width = ABS (area.width); - area.x -= area.width; - } - - gtk_snapshot_render_background (draw->snapshot, draw->style_context, area.x + 2, area.y + 2, area.width - 4, area.height - 4); -} - -static void -sysprof_visualizers_frame_snapshot (GtkWidget *widget, - GtkSnapshot *snapshot) -{ - SysprofVisualizersFrame *self = (SysprofVisualizersFrame *)widget; - SelectionDraw draw; - GtkAllocation alloc; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (GTK_IS_SNAPSHOT (snapshot)); - - GTK_WIDGET_CLASS (sysprof_visualizers_frame_parent_class)->snapshot (widget, snapshot); - - draw.duration = sysprof_visualizer_get_duration (SYSPROF_VISUALIZER (self->ticks)); - if (draw.duration == 0) - return; - - draw.style_context = gtk_widget_get_style_context (GTK_WIDGET (self->visualizers)); - draw.list = self->visualizers; - draw.snapshot = snapshot; - draw.begin_time = self->begin_time; - - gtk_widget_get_allocation (GTK_WIDGET (self->visualizers), &alloc); - draw.width = alloc.width; - draw.height = alloc.height; - - if (sysprof_selection_get_has_selection (self->selection) || self->button_pressed) - { - double x, y; - - gtk_snapshot_save (snapshot); - gtk_widget_translate_coordinates (GTK_WIDGET (self->visualizers), - GTK_WIDGET (self), - 0, 0, &x, &y); - gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); - - gtk_style_context_add_class (draw.style_context, "selection"); - sysprof_selection_foreach (self->selection, draw_selection_cb, &draw); - if (self->button_pressed) - draw_selection_cb (self->selection, self->drag_begin_at, self->drag_selection_at, &draw); - gtk_style_context_remove_class (draw.style_context, "selection"); - - gtk_snapshot_restore (snapshot); - } -} - -static void -visualizers_button_press_event_cb (SysprofVisualizersFrame *self, - int n_presses, - double x, - double y, - GtkGestureClick *gesture) -{ - GdkModifierType state; - guint button; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (GTK_IS_GESTURE_CLICK (gesture)); - - button = gtk_gesture_single_get_button (GTK_GESTURE_SINGLE (gesture)); - - if (button != GDK_BUTTON_PRIMARY) - { - if (sysprof_selection_get_has_selection (self->selection)) - sysprof_selection_unselect_all (self->selection); - return; - } - - state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (gesture)); - - if ((state & GDK_SHIFT_MASK) == 0) - sysprof_selection_unselect_all (self->selection); - - self->button_pressed = TRUE; - - self->drag_begin_at = get_time_from_x (self, x); - self->drag_selection_at = self->drag_begin_at; - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static void -visualizers_button_release_event_cb (SysprofVisualizersFrame *self, - int n_release, - double x, - double y, - GtkGestureClick *gesture) -{ - guint button; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (GTK_IS_GESTURE_CLICK (gesture)); - - button = gtk_gesture_single_get_button (GTK_GESTURE_SINGLE (gesture)); - - if (!self->button_pressed || button != GDK_BUTTON_PRIMARY) - return; - - self->button_pressed = FALSE; - - if (self->drag_begin_at != self->drag_selection_at) - { - sysprof_selection_select_range (self->selection, - self->drag_begin_at, - self->drag_selection_at); - self->drag_begin_at = -1; - self->drag_selection_at = -1; - } - - gtk_widget_queue_draw (GTK_WIDGET (self)); -} - -static void -visualizers_motion_notify_event_cb (SysprofVisualizersFrame *self, - double x, - double y, - GtkEventControllerMotion *motion) -{ - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); - - if (self->button_pressed) - { - self->drag_selection_at = get_time_from_x (self, x); - gtk_widget_queue_draw (GTK_WIDGET (self)); - } -} - -static void -set_children_width_request (GtkWidget *widget, - int width) -{ - for (GtkWidget *child = gtk_widget_get_first_child (widget); - child; - child = gtk_widget_get_next_sibling (child)) - gtk_widget_set_size_request (child, width, -1); -} - -static void -sysprof_visualizers_frame_notify_zoom (SysprofVisualizersFrame *self, - GParamSpec *pspec, - SysprofZoomManager *zoom_manager) -{ - gint64 duration; - gint data_width; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (SYSPROF_IS_ZOOM_MANAGER (zoom_manager)); - - duration = self->end_time - self->begin_time; - data_width = sysprof_zoom_manager_get_width_for_duration (self->zoom_manager, duration); - set_children_width_request (GTK_WIDGET (self->ticks_viewport), data_width); - set_children_width_request (GTK_WIDGET (self->visualizers_viewport), data_width); -} - -static gint -find_pos (SysprofVisualizersFrame *self, - const gchar *title, - gint priority) -{ - gint pos = 0; - - if (title == NULL) - return -1; - - for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->visualizers)); - child; - child = gtk_widget_get_next_sibling (child)) - { - SysprofVisualizerGroup *group = SYSPROF_VISUALIZER_GROUP (child); - gint prio = sysprof_visualizer_group_get_priority (group); - const gchar *item = sysprof_visualizer_group_get_title (group); - - if (priority < prio || - (priority == prio && g_strcmp0 (title, item) < 0)) - break; - - pos++; - } - - return pos; -} - -void -sysprof_visualizers_frame_add_group (SysprofVisualizersFrame *self, - SysprofVisualizerGroup *group) -{ - SysprofVisualizerGroupHeader *header; - const char *title; - int priority; - int pos; - - g_return_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_return_if_fail (SYSPROF_IS_VISUALIZER_GROUP (group)); - - title = sysprof_visualizer_group_get_title (group); - priority = sysprof_visualizer_group_get_priority (group); - pos = find_pos (self, title, priority); - - gtk_list_box_insert (self->visualizers, GTK_WIDGET (group), pos); - - header = _sysprof_visualizer_group_header_new (group); - gtk_list_box_insert (self->groups, GTK_WIDGET (header), pos); - _sysprof_visualizer_group_set_header (group, header); - gtk_widget_show (GTK_WIDGET (header)); - - sysprof_visualizers_frame_notify_zoom (self, NULL, self->zoom_manager); -} - -static void -sysprof_visualizers_frame_selection_changed (SysprofVisualizersFrame *self, - SysprofSelection *selection) -{ - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (SYSPROF_IS_SELECTION (selection)); - - gtk_widget_queue_draw (GTK_WIDGET (self->visualizers)); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SELECTION]); -} - -static void -sysprof_visualizers_frame_group_activated_cb (SysprofVisualizersFrame *self, - SysprofVisualizerGroupHeader *row, - GtkListBox *list) -{ - SysprofVisualizerGroup *group; - - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_assert (SYSPROF_IS_VISUALIZER_GROUP_HEADER (row)); - - group = _sysprof_visualizer_group_header_get_group (row); - g_assert (SYSPROF_IS_VISUALIZER_GROUP (group)); - - g_signal_emit_by_name (group, "group-activated"); -} - -static void -sysprof_visualizers_frame_dispose (GObject *object) -{ - SysprofVisualizersFrame *self = (SysprofVisualizersFrame *)object; - GtkWidget *child; - - while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) - gtk_widget_unparent (child); - - g_clear_object (&self->selection); - - G_OBJECT_CLASS (sysprof_visualizers_frame_parent_class)->dispose (object); -} - -static void -sysprof_visualizers_frame_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofVisualizersFrame *self = SYSPROF_VISUALIZERS_FRAME (object); - - switch (prop_id) - { - case PROP_SELECTED_GROUP: - g_value_set_object (value, sysprof_visualizers_frame_get_selected_group (self)); - break; - - case PROP_SELECTION: - g_value_set_object (value, sysprof_visualizers_frame_get_selection (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_visualizers_frame_class_init (SysprofVisualizersFrameClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - - object_class->dispose = sysprof_visualizers_frame_dispose; - object_class->get_property = sysprof_visualizers_frame_get_property; - - widget_class->snapshot = sysprof_visualizers_frame_snapshot; - - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-visualizers-frame.ui"); - gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); - gtk_widget_class_set_css_name (widget_class, "SysprofVisualizersFrame"); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, groups); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, hscrollbar); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, hscroller); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, left_column); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, ticks); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, ticks_scroller); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, visualizers); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, vscroller); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, zoom_manager); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, zoom_scale); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, ticks_viewport); - gtk_widget_class_bind_template_child (widget_class, SysprofVisualizersFrame, visualizers_viewport); - - properties [PROP_SELECTED_GROUP] = - g_param_spec_object ("selected-group", - "Selected Group", - "The selected group", - SYSPROF_TYPE_VISUALIZER_GROUP, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_SELECTION] = - g_param_spec_object ("selection", - "Selection", - "The time selection", - SYSPROF_TYPE_SELECTION, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - g_type_ensure (SYSPROF_TYPE_SCROLLMAP); - g_type_ensure (SYSPROF_TYPE_VISUALIZER_TICKS); - g_type_ensure (SYSPROF_TYPE_ZOOM_MANAGER); -} - -static void -sysprof_visualizers_frame_init (SysprofVisualizersFrame *self) -{ - GtkEventController *controller; - GtkAdjustment *hadj; - GtkAdjustment *zadj; - - gtk_widget_init_template (GTK_WIDGET (self)); - - gtk_widget_set_cursor_from_name (GTK_WIDGET (self->visualizers), "text"); - - controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); - g_signal_connect_object (controller, - "pressed", - G_CALLBACK (visualizers_button_press_event_cb), - self, - G_CONNECT_SWAPPED); - g_signal_connect_object (controller, - "released", - G_CALLBACK (visualizers_button_release_event_cb), - self, - G_CONNECT_SWAPPED); - gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); - gtk_widget_add_controller (GTK_WIDGET (self->visualizers), controller); - - controller = gtk_event_controller_motion_new (); - g_signal_connect_object (controller, - "motion", - G_CALLBACK (visualizers_motion_notify_event_cb), - self, - G_CONNECT_SWAPPED); - gtk_widget_add_controller (GTK_WIDGET (self->visualizers), controller); - - self->selection = g_object_new (SYSPROF_TYPE_SELECTION, NULL); - - zadj = sysprof_zoom_manager_get_adjustment (self->zoom_manager); - hadj = gtk_scrolled_window_get_hadjustment (self->hscroller); - - gtk_scrolled_window_set_hadjustment (self->ticks_scroller, hadj); - sysprof_scrollmap_set_adjustment (self->hscrollbar, hadj); - gtk_range_set_adjustment (GTK_RANGE (self->zoom_scale), zadj); - - gtk_widget_insert_action_group (GTK_WIDGET (self), - "zoom", - G_ACTION_GROUP (self->zoom_manager)); - - g_signal_connect_object (self->groups, - "row-activated", - G_CALLBACK (sysprof_visualizers_frame_group_activated_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->selection, - "changed", - G_CALLBACK (sysprof_visualizers_frame_selection_changed), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->zoom_manager, - "notify::zoom", - G_CALLBACK (sysprof_visualizers_frame_notify_zoom), - self, - G_CONNECT_SWAPPED | G_CONNECT_AFTER); -} - -/** - * sysprof_visualizers_frame_get_selected_group: - * - * Gets the currently selected group. - * - * Returns: (transfer none) (nullable): the selected row - * - * Since: 3.34 - */ -SysprofVisualizerGroup * -sysprof_visualizers_frame_get_selected_group (SysprofVisualizersFrame *self) -{ - GtkListBoxRow *row; - - g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); - - row = gtk_list_box_get_selected_row (self->groups); - - return SYSPROF_VISUALIZER_GROUP (row); -} - -/** - * sysprof_visualizers_frame_get_selection: - * - * Get the time selection - * - * Returns: (transfer none): a #SysprofSelection - * - * Since: 3.34 - */ -SysprofSelection * -sysprof_visualizers_frame_get_selection (SysprofVisualizersFrame *self) -{ - g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); - - return self->selection; -} - -static gint -compare_gint64 (const gint64 *a, - const gint64 *b) -{ - if (*a < *b) - return -1; - else if (*a > *b) - return 1; - else - return 0; -} - -static bool -index_frame_times_frame_cb (const SysprofCaptureFrame *frame, - gpointer user_data) -{ - GArray *array = user_data; - - /* Track timing, but ignore some common types at startup */ - if (frame->type != SYSPROF_CAPTURE_FRAME_MAP && - frame->type != SYSPROF_CAPTURE_FRAME_PROCESS) - g_array_append_val (array, frame->time); - - return TRUE; -} - -static void -index_frame_times_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofCaptureCursor *cursor = task_data; - GArray *timings = NULL; - - g_assert (G_IS_TASK (task)); - g_assert (SYSPROF_IS_VISUALIZERS_FRAME (source_object)); - g_assert (cursor != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - timings = g_array_new (FALSE, FALSE, sizeof (gint64)); - sysprof_capture_cursor_foreach (cursor, index_frame_times_frame_cb, timings); - g_array_sort (timings, (GCompareFunc) compare_gint64); - - g_task_return_pointer (task, - g_steal_pointer (&timings), - (GDestroyNotify) g_array_unref); -} - -void -sysprof_visualizers_frame_load_async (SysprofVisualizersFrame *self, - SysprofCaptureReader *reader, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - GtkAllocation alloc; - - g_return_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self)); - g_return_if_fail (reader != NULL); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - gtk_widget_get_allocation (GTK_WIDGET (self->ticks), &alloc); - - /* At this point, the SysprofDisplay should have already scanned the - * reader for all of the events and therefore we can trust the begin - * and end time for the capture. - */ - self->begin_time = sysprof_capture_reader_get_start_time (reader); - self->end_time = sysprof_capture_reader_get_end_time (reader); - - /* Now we need to run through the frames and index their times - * so that we can calculate the number of items per bucket when - * drawing the scrollbar. - */ - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_visualizers_frame_load_async); - g_task_set_task_data (task, - sysprof_capture_cursor_new (reader), - (GDestroyNotify) sysprof_capture_cursor_unref); - g_task_run_in_thread (task, index_frame_times_worker); -} - -gboolean -sysprof_visualizers_frame_load_finish (SysprofVisualizersFrame *self, - GAsyncResult *result, - GError **error) -{ - g_autoptr(GArray) timings = NULL; - - g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - if ((timings = g_task_propagate_pointer (G_TASK (result), error))) - { - sysprof_scrollmap_set_timings (self->hscrollbar, timings); - sysprof_scrollmap_set_time_range (self->hscrollbar, self->begin_time, self->end_time); - sysprof_visualizer_set_time_range (SYSPROF_VISUALIZER (self->ticks), self->begin_time, self->end_time); - gtk_widget_queue_resize (GTK_WIDGET (self)); - - return TRUE; - } - - return FALSE; -} - -/** - * sysprof_visualizers_frame_get_zoom_manager: - * - * Gets the zoom manager for the frame. - * - * Returns: (transfer none): a #SysprofZoomManager - */ -SysprofZoomManager * -sysprof_visualizers_frame_get_zoom_manager (SysprofVisualizersFrame *self) -{ - g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); - - return self->zoom_manager; -} - -/** - * sysprof_visualizers_frame_get_size_group: - * - * gets the left column size group. - * - * Returns: (transfer none): a size group - */ -GtkSizeGroup * -sysprof_visualizers_frame_get_size_group (SysprofVisualizersFrame *self) -{ - g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); - - return self->left_column; -} - -/** - * sysprof_visualizers_frame_get_hadjustment: - * - * Gets the scroll adjustment used for horizontal scrolling - * - * Returns: (transfer none): a #GtkAdjustment - */ -GtkAdjustment * -sysprof_visualizers_frame_get_hadjustment (SysprofVisualizersFrame *self) -{ - g_return_val_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self), NULL); - - return sysprof_scrollmap_get_adjustment (self->hscrollbar); -} - -void -sysprof_visualizers_frame_unselect_row (SysprofVisualizersFrame *self) -{ - g_return_if_fail (SYSPROF_IS_VISUALIZERS_FRAME (self)); - - gtk_list_box_unselect_all (self->groups); -} diff --git a/src/libsysprof-ui/sysprof-visualizers-frame.h b/src/libsysprof-ui/sysprof-visualizers-frame.h deleted file mode 100644 index 3217ab57..00000000 --- a/src/libsysprof-ui/sysprof-visualizers-frame.h +++ /dev/null @@ -1,52 +0,0 @@ -/* sysprof-visualizers-frame.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include -#include - -#include "sysprof-visualizer-group.h" -#include "sysprof-zoom-manager.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_VISUALIZERS_FRAME (sysprof_visualizers_frame_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofVisualizersFrame, sysprof_visualizers_frame, SYSPROF, VISUALIZERS_FRAME, GtkWidget) - -void sysprof_visualizers_frame_add_group (SysprofVisualizersFrame *self, - SysprofVisualizerGroup *group); -SysprofSelection *sysprof_visualizers_frame_get_selection (SysprofVisualizersFrame *self); -SysprofVisualizerGroup *sysprof_visualizers_frame_get_selected_group (SysprofVisualizersFrame *self); -SysprofZoomManager *sysprof_visualizers_frame_get_zoom_manager (SysprofVisualizersFrame *self); -void sysprof_visualizers_frame_load_async (SysprofVisualizersFrame *self, - SysprofCaptureReader *reader, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_visualizers_frame_load_finish (SysprofVisualizersFrame *self, - GAsyncResult *result, - GError **error); -GtkSizeGroup *sysprof_visualizers_frame_get_size_group (SysprofVisualizersFrame *self); -GtkAdjustment *sysprof_visualizers_frame_get_hadjustment (SysprofVisualizersFrame *self); -void sysprof_visualizers_frame_unselect_row (SysprofVisualizersFrame *self); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-visualizers-frame.ui b/src/libsysprof-ui/sysprof-visualizers-frame.ui deleted file mode 100644 index 4ff60076..00000000 --- a/src/libsysprof-ui/sysprof-visualizers-frame.ui +++ /dev/null @@ -1,242 +0,0 @@ - - - - - - - - - - - - vertical - - - - - - - - - - - - - diff --git a/src/libsysprof-ui/sysprof-zoom-manager.c b/src/libsysprof-ui/sysprof-zoom-manager.c deleted file mode 100644 index 679205e0..00000000 --- a/src/libsysprof-ui/sysprof-zoom-manager.c +++ /dev/null @@ -1,661 +0,0 @@ -/* sysprof-zoom-manager.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-zoom-manager" - -#include "config.h" - -#include -#include -#include -#include - -#include "sysprof-zoom-manager.h" - -#define DEFAULT_PIXELS_PER_SEC (20.0) - -struct _SysprofZoomManager -{ - GObject parent_instance; - - GtkAdjustment *adjustment; - GSimpleActionGroup *actions; - - gdouble min_zoom; - gdouble max_zoom; - gdouble zoom; -} __attribute__((aligned(8))); - -enum { - PROP_0, - PROP_CAN_ZOOM_IN, - PROP_CAN_ZOOM_OUT, - PROP_MIN_ZOOM, - PROP_MAX_ZOOM, - PROP_ZOOM, - PROP_ZOOM_LABEL, - N_PROPS -}; - -static void action_group_iface_init (GActionGroupInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofZoomManager, sysprof_zoom_manager, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, action_group_iface_init)) - -static GParamSpec *properties [N_PROPS]; -static gdouble zoom_levels[] = { - 0.3, 0.5, 0.67, 0.80, 0.90, - 1.0, 1.5, 2.0, 2.5, 3.0, - 5.0, 10.0, 20.0, 30.0, 50.0, -}; - -static void -sysprof_zoom_manager_zoom_in_action (GSimpleAction *action, - GVariant *param, - gpointer user_data) -{ - SysprofZoomManager *self = user_data; - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - sysprof_zoom_manager_zoom_in (self); -} - -static void -sysprof_zoom_manager_zoom_out_action (GSimpleAction *action, - GVariant *param, - gpointer user_data) -{ - SysprofZoomManager *self = user_data; - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - sysprof_zoom_manager_zoom_out (self); -} - -static void -sysprof_zoom_manager_zoom_one_action (GSimpleAction *action, - GVariant *param, - gpointer user_data) -{ - SysprofZoomManager *self = user_data; - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - sysprof_zoom_manager_reset (self); -} - -static void -sysprof_zoom_manager_zoom_action (GSimpleAction *action, - GVariant *param, - gpointer user_data) -{ - SysprofZoomManager *self = user_data; - - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - g_assert (g_variant_is_of_type (param, G_VARIANT_TYPE_DOUBLE)); - - sysprof_zoom_manager_set_zoom (self, g_variant_get_double (param)); -} - -static void -sysprof_zoom_manager_value_changed_cb (SysprofZoomManager *self, - GtkAdjustment *adjustment) -{ - gdouble value; - - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - g_assert (GTK_IS_ADJUSTMENT (adjustment)); - - value = gtk_adjustment_get_value (adjustment); - - if (value == 0.0) - value = 1.0; - else if (value > 0.0) - value = (value + 1.0) * (value + 1.0); - else - value = 1.0 / ABS (value); - - sysprof_zoom_manager_set_zoom (self, value); -} - -static void -sysprof_zoom_manager_finalize (GObject *object) -{ - SysprofZoomManager *self = (SysprofZoomManager *)object; - - g_clear_object (&self->actions); - g_clear_object (&self->adjustment); - - G_OBJECT_CLASS (sysprof_zoom_manager_parent_class)->finalize (object); -} - -static void -sysprof_zoom_manager_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofZoomManager *self = SYSPROF_ZOOM_MANAGER (object); - - switch (prop_id) - { - case PROP_MIN_ZOOM: - g_value_set_double (value, sysprof_zoom_manager_get_min_zoom (self)); - break; - - case PROP_MAX_ZOOM: - g_value_set_double (value, sysprof_zoom_manager_get_max_zoom (self)); - break; - - case PROP_ZOOM: - g_value_set_double (value, sysprof_zoom_manager_get_zoom (self)); - break; - - case PROP_ZOOM_LABEL: - g_value_take_string (value, sysprof_zoom_manager_get_zoom_label (self)); - break; - - case PROP_CAN_ZOOM_IN: - g_value_set_boolean (value, sysprof_zoom_manager_get_can_zoom_in (self)); - break; - - case PROP_CAN_ZOOM_OUT: - g_value_set_boolean (value, sysprof_zoom_manager_get_can_zoom_out (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_zoom_manager_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofZoomManager *self = SYSPROF_ZOOM_MANAGER (object); - - switch (prop_id) - { - case PROP_MIN_ZOOM: - sysprof_zoom_manager_set_min_zoom (self, g_value_get_double (value)); - break; - - case PROP_MAX_ZOOM: - sysprof_zoom_manager_set_max_zoom (self, g_value_get_double (value)); - break; - - case PROP_ZOOM: - sysprof_zoom_manager_set_zoom (self, g_value_get_double (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_zoom_manager_class_init (SysprofZoomManagerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_zoom_manager_finalize; - object_class->get_property = sysprof_zoom_manager_get_property; - object_class->set_property = sysprof_zoom_manager_set_property; - - properties [PROP_CAN_ZOOM_IN] = - g_param_spec_boolean ("can-zoom-in", - "Can Zoom In", - "Can Zoom In", - TRUE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_CAN_ZOOM_OUT] = - g_param_spec_boolean ("can-zoom-out", - "Can Zoom Out", - "Can Zoom Out", - TRUE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_MIN_ZOOM] = - g_param_spec_double ("min-zoom", - "Min Zoom", - "The minimum zoom to apply", - -G_MAXDOUBLE, - G_MAXDOUBLE, - 0.0, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_MAX_ZOOM] = - g_param_spec_double ("max-zoom", - "Max Zoom", - "The maximum zoom to apply", - -G_MAXDOUBLE, - G_MAXDOUBLE, - 0.0, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_ZOOM] = - g_param_spec_double ("zoom", - "Zoom", - "The current zoom level", - -G_MAXDOUBLE, - G_MAXDOUBLE, - 1.0, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - properties [PROP_ZOOM_LABEL] = - g_param_spec_string ("zoom-label", NULL, NULL, - NULL, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_zoom_manager_init (SysprofZoomManager *self) -{ - static const GActionEntry entries[] = { - { "zoom-in", sysprof_zoom_manager_zoom_in_action }, - { "zoom-out", sysprof_zoom_manager_zoom_out_action }, - { "zoom-one", sysprof_zoom_manager_zoom_one_action }, - { "zoom", NULL, "d", "1.0", sysprof_zoom_manager_zoom_action }, - }; - GAction *action; - - self->min_zoom = 0.00001; - self->max_zoom = 10000.0; - self->zoom = 1.0; - - self->adjustment = g_object_ref_sink (gtk_adjustment_new (0, -10, 10, 1, 3, 0)); - - g_signal_connect_object (self->adjustment, - "value-changed", - G_CALLBACK (sysprof_zoom_manager_value_changed_cb), - self, - G_CONNECT_SWAPPED); - - self->actions = g_simple_action_group_new (); - g_action_map_add_action_entries (G_ACTION_MAP (self->actions), - entries, - G_N_ELEMENTS (entries), - self); - - action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom-in"); - g_object_bind_property (self, "can-zoom-in", action, "enabled", G_BINDING_SYNC_CREATE); - - action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom-out"); - g_object_bind_property (self, "can-zoom-out", action, "enabled", G_BINDING_SYNC_CREATE); -} - -SysprofZoomManager * -sysprof_zoom_manager_new (void) -{ - return g_object_new (SYSPROF_TYPE_ZOOM_MANAGER, NULL); -} - -gboolean -sysprof_zoom_manager_get_can_zoom_in (SysprofZoomManager *self) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), FALSE); - - return self->max_zoom == 0.0 || self->zoom < self->max_zoom; -} - -gboolean -sysprof_zoom_manager_get_can_zoom_out (SysprofZoomManager *self) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), FALSE); - - return self->min_zoom == 0.0 || self->zoom > self->min_zoom; -} - -gboolean -sysprof_zoom_manager_get_min_zoom (SysprofZoomManager *self) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), FALSE); - - return self->min_zoom; -} - -gboolean -sysprof_zoom_manager_get_max_zoom (SysprofZoomManager *self) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), FALSE); - - return self->max_zoom; -} - -void -sysprof_zoom_manager_set_min_zoom (SysprofZoomManager *self, - gdouble min_zoom) -{ - g_return_if_fail (SYSPROF_IS_ZOOM_MANAGER (self)); - - if (min_zoom != self->min_zoom) - { - self->min_zoom = min_zoom; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MIN_ZOOM]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_OUT]); - } -} - -void -sysprof_zoom_manager_set_max_zoom (SysprofZoomManager *self, - gdouble max_zoom) -{ - g_return_if_fail (SYSPROF_IS_ZOOM_MANAGER (self)); - - if (max_zoom != self->max_zoom) - { - self->max_zoom = max_zoom; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MAX_ZOOM]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_IN]); - } -} - -void -sysprof_zoom_manager_zoom_in (SysprofZoomManager *self) -{ - gdouble zoom; - - g_return_if_fail (SYSPROF_IS_ZOOM_MANAGER (self)); - - if (!sysprof_zoom_manager_get_can_zoom_in (self)) - return; - - zoom = self->zoom; - - for (guint i = 0; i < G_N_ELEMENTS (zoom_levels); i++) - { - if (zoom_levels[i] > zoom) - { - zoom = zoom_levels[i]; - break; - } - } - - if (zoom == self->zoom) - zoom *= 2; - - sysprof_zoom_manager_set_zoom (self, zoom); -} - -void -sysprof_zoom_manager_zoom_out (SysprofZoomManager *self) -{ - gdouble zoom; - - g_return_if_fail (SYSPROF_IS_ZOOM_MANAGER (self)); - - if (!sysprof_zoom_manager_get_can_zoom_out (self)) - return; - - zoom = self->zoom; - - for (guint i = G_N_ELEMENTS (zoom_levels); i > 0; i--) - { - if (zoom_levels[i-1] < zoom) - { - zoom = zoom_levels[i-1]; - break; - } - } - - if (zoom == self->zoom) - zoom /= 2.0; - - sysprof_zoom_manager_set_zoom (self, zoom); -} - -void -sysprof_zoom_manager_reset (SysprofZoomManager *self) -{ - g_return_if_fail (SYSPROF_IS_ZOOM_MANAGER (self)); - - sysprof_zoom_manager_set_zoom (self, 1.0); -} - -gdouble -sysprof_zoom_manager_get_zoom (SysprofZoomManager *self) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), 0.0); - - return self->zoom; -} - -void -sysprof_zoom_manager_set_zoom (SysprofZoomManager *self, - gdouble zoom) -{ - gdouble min_zoom; - gdouble max_zoom; - - g_return_if_fail (SYSPROF_IS_ZOOM_MANAGER (self)); - - min_zoom = (self->min_zoom == 0.0) ? -G_MAXDOUBLE : self->min_zoom; - max_zoom = (self->max_zoom == 0.0) ? G_MAXDOUBLE : self->max_zoom; - - zoom = CLAMP (zoom, min_zoom, max_zoom); - - if (zoom == 0.0) - zoom = 1.0; - - if (zoom != self->zoom) - { - g_autoptr(GVariant) state = NULL; - gdouble adj; - - self->zoom = zoom; - state = g_variant_take_ref (g_variant_new_double (zoom)); - - g_object_set (g_action_map_lookup_action (G_ACTION_MAP (self->actions), "zoom"), - "state", state, - NULL); - - /* Convert to form used by adjustment */ - if (zoom == 1.0) - adj = 0.0; - else if (zoom > 1.0) - adj = sqrt (zoom) - 1.0; - else - adj = -1 / zoom; - - g_signal_handlers_block_matched (self->adjustment, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, - sysprof_zoom_manager_value_changed_cb, self); - gtk_adjustment_set_value (self->adjustment, adj); - g_signal_handlers_unblock_matched (self->adjustment, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, - sysprof_zoom_manager_value_changed_cb, self); - - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_IN]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CAN_ZOOM_OUT]); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ZOOM_LABEL]); - } -} - -static gchar ** -sysprof_zoom_manager_list_actions (GActionGroup *action_group) -{ - SysprofZoomManager *self = (SysprofZoomManager *)action_group; - - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - - return g_action_group_list_actions (G_ACTION_GROUP (self->actions)); -} - -static gboolean -sysprof_zoom_manager_query_action (GActionGroup *action_group, - const gchar *action_name, - gboolean *enabled, - const GVariantType **parameter_type, - const GVariantType **state_type, - GVariant **state_hint, - GVariant **state) -{ - SysprofZoomManager *self = (SysprofZoomManager *)action_group; - - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - g_assert (action_name != NULL); - - return g_action_group_query_action (G_ACTION_GROUP (self->actions), - action_name, - enabled, - parameter_type, - state_type, - state_hint, - state); -} - -static void -sysprof_zoom_manager_change_action_state (GActionGroup *action_group, - const gchar *action_name, - GVariant *value) -{ - SysprofZoomManager *self = (SysprofZoomManager *)action_group; - - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - g_assert (action_name != NULL); - - g_action_group_change_action_state (G_ACTION_GROUP (self->actions), action_name, value); -} - -static void -sysprof_zoom_manager_activate_action (GActionGroup *action_group, - const gchar *action_name, - GVariant *parameter) -{ - SysprofZoomManager *self = (SysprofZoomManager *)action_group; - - g_assert (SYSPROF_IS_ZOOM_MANAGER (self)); - g_assert (action_name != NULL); - - g_action_group_activate_action (G_ACTION_GROUP (self->actions), action_name, parameter); -} - -static void -action_group_iface_init (GActionGroupInterface *iface) -{ - iface->list_actions = sysprof_zoom_manager_list_actions; - iface->query_action = sysprof_zoom_manager_query_action; - iface->change_action_state = sysprof_zoom_manager_change_action_state; - iface->activate_action = sysprof_zoom_manager_activate_action; -} - -/** - * sysprof_zoom_manager_get_zoom_level: - * @self: a #SysprofZoomManager - * - * Returns: (transfer full): a string containing the zoom percentage label - * - * Since: 3.34 - */ -gchar * -sysprof_zoom_manager_get_zoom_label (SysprofZoomManager *self) -{ - gdouble percent; - - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), NULL); - - percent = self->zoom * 100.0; - - if (percent < 1.0) - return g_strdup_printf ("%0.2lf%%", percent); - else - return g_strdup_printf ("%d%%", (gint)percent); -} - -gint64 -sysprof_zoom_manager_get_duration_for_width (SysprofZoomManager *self, - gint width) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), 0); - - return SYSPROF_NSEC_PER_SEC * ((gdouble)width / (DEFAULT_PIXELS_PER_SEC * self->zoom)); -} - -gint -sysprof_zoom_manager_get_width_for_duration (SysprofZoomManager *self, - gint64 duration) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), 0); - - return (gdouble)duration / (gdouble)SYSPROF_NSEC_PER_SEC * DEFAULT_PIXELS_PER_SEC * self->zoom; -} - -gdouble -sysprof_zoom_manager_fit_zoom_for_duration (SysprofZoomManager *self, - gint64 duration, - gint width) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), 1.0); - g_return_val_if_fail (duration >= 0, 1.0); - g_return_val_if_fail (width >= 0, 1.0); - - return (width / DEFAULT_PIXELS_PER_SEC) / (duration / (gdouble)SYSPROF_NSEC_PER_SEC); -} - -gdouble -sysprof_zoom_manager_get_offset_at_time (SysprofZoomManager *self, - gint64 offset, - gint width) -{ - gint64 full_duration; - gdouble ratio; - - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), 0.0); - - full_duration = sysprof_zoom_manager_get_duration_for_width (self, width); - ratio = offset / (gdouble)full_duration; - return ratio * width; -} - -gchar * -_sysprof_format_duration (gint64 duration) -{ - gboolean negative = duration < 0; - - if (duration == 0) - return g_strdup ("0"); - - duration = ABS (duration); - - if (duration < SYSPROF_NSEC_PER_SEC) - return g_strdup_printf ("%s%.3lf msec", - negative ? "-" : "", - (duration / (gdouble)SYSPROF_NSEC_PER_SEC * 1000L)); - else - return g_strdup_printf ("%s%.4lf seconds", - negative ? "-" : "", - (duration / (gdouble)SYSPROF_NSEC_PER_SEC)); -} - -/** - * sysprof_zoom_manager_get_adjustment: - * - * Gets an adjustment for zoom controls. - * - * Returns: (transfer none): a #GtkAdjustment - * - * Since: 3.34 - */ -GtkAdjustment * -sysprof_zoom_manager_get_adjustment (SysprofZoomManager *self) -{ - g_return_val_if_fail (SYSPROF_IS_ZOOM_MANAGER (self), NULL); - - return self->adjustment; -} diff --git a/src/libsysprof-ui/sysprof-zoom-manager.h b/src/libsysprof-ui/sysprof-zoom-manager.h deleted file mode 100644 index d25742f0..00000000 --- a/src/libsysprof-ui/sysprof-zoom-manager.h +++ /dev/null @@ -1,59 +0,0 @@ -/* sysprof-zoom-manager.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_ZOOM_MANAGER (sysprof_zoom_manager_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofZoomManager, sysprof_zoom_manager, SYSPROF, ZOOM_MANAGER, GObject) - -SysprofZoomManager *sysprof_zoom_manager_new (void); -GtkAdjustment *sysprof_zoom_manager_get_adjustment (SysprofZoomManager *self); -gboolean sysprof_zoom_manager_get_can_zoom_in (SysprofZoomManager *self); -gboolean sysprof_zoom_manager_get_can_zoom_out (SysprofZoomManager *self); -gboolean sysprof_zoom_manager_get_min_zoom (SysprofZoomManager *self); -gboolean sysprof_zoom_manager_get_max_zoom (SysprofZoomManager *self); -void sysprof_zoom_manager_set_min_zoom (SysprofZoomManager *self, - gdouble min_zoom); -void sysprof_zoom_manager_set_max_zoom (SysprofZoomManager *self, - gdouble max_zoom); -void sysprof_zoom_manager_zoom_in (SysprofZoomManager *self); -void sysprof_zoom_manager_zoom_out (SysprofZoomManager *self); -void sysprof_zoom_manager_reset (SysprofZoomManager *self); -gdouble sysprof_zoom_manager_get_zoom (SysprofZoomManager *self); -void sysprof_zoom_manager_set_zoom (SysprofZoomManager *self, - gdouble zoom); -gchar *sysprof_zoom_manager_get_zoom_label (SysprofZoomManager *self); -gint sysprof_zoom_manager_get_width_for_duration (SysprofZoomManager *self, - gint64 duration); -gint64 sysprof_zoom_manager_get_duration_for_width (SysprofZoomManager *self, - gint width); -gdouble sysprof_zoom_manager_fit_zoom_for_duration (SysprofZoomManager *self, - gint64 duration, - gint width); -gdouble sysprof_zoom_manager_get_offset_at_time (SysprofZoomManager *self, - gint64 offset, - gint width); - -G_END_DECLS diff --git a/src/libsysprof/binfile.c b/src/libsysprof/binfile.c deleted file mode 100644 index a68598ff..00000000 --- a/src/libsysprof/binfile.c +++ /dev/null @@ -1,576 +0,0 @@ -/* MemProf -- memory profiler and leak detector - * Copyright 1999, 2000, 2001, Red Hat, Inc. - * Copyright 2002, Kristian Rietveld - * - * Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, 2005, 2006, 2007, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -/* Most interesting code in this file is lifted from bfdutils.c - * and process.c from Memprof, - */ -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "binfile.h" -#include "elfparser.h" - -struct bin_file_t -{ - int ref_count; - - GList * elf_files; - - char * filename; - - char * undefined_name; - - gulong text_offset; - - gboolean inode_check; - ino_t inode; -}; - -static ino_t -read_inode (const char *filename) -{ - struct stat statbuf; - - if (strcmp (filename, "[vdso]") == 0) - return (ino_t)0; - - if (stat (filename, &statbuf) < 0) - return (ino_t)-1; - - return statbuf.st_ino; -} - -static gboolean -already_warned (const char *name) -{ - static GPtrArray *warnings; - guint i; - - if (!warnings) - warnings = g_ptr_array_new (); - - for (i = 0; i < warnings->len; ++i) - { - if (strcmp (warnings->pdata[i], name) == 0) - return TRUE; - } - - g_ptr_array_add (warnings, g_strdup (name)); - - return FALSE; -} - -static ElfParser * -get_build_id_file (ElfParser *elf, - const char * const *debug_dirs) -{ - const char *build_id; - GList *tries = NULL, *list; - char *init, *rest; - ElfParser *result = NULL; - char *tmp; - - build_id = elf_parser_get_build_id (elf); - - if (!build_id) - return NULL; - - if (strlen (build_id) < 4) - return NULL; - - init = g_strndup (build_id, 2); - rest = g_strdup_printf ("%s%s", build_id + 2, ".debug"); - - if (debug_dirs) - { - for (guint i = 0; debug_dirs[i]; i++) - { - tmp = g_build_filename (debug_dirs[i], ".build-id", init, rest, NULL); - tries = g_list_append (tries, tmp); - } - } - - for (list = tries; list != NULL; list = list->next) - { - char *name = list->data; - ElfParser *parser = elf_parser_new (name, NULL); - - if (parser) - { - const char *file_id = elf_parser_get_build_id (parser); - - if (file_id && strcmp (build_id, file_id) == 0) - { - result = parser; - break; - } - - elf_parser_free (parser); - } - } - - g_list_foreach (tries, (GFunc)g_free, NULL); - g_list_free (tries); - - g_free (init); - g_free (rest); - - return result; -} - -static ElfParser * -get_debuglink_file (ElfParser *elf, - const char *filename, - char **new_name, - const gchar * const *debug_dirs) -{ - const char *basename; - guint32 crc32; - ElfParser *result = NULL; - const char *build_id; - char *dir; - const char *files; - const char *prefix = ""; - - if (!elf) - return NULL; - - basename = elf_parser_get_debug_link (elf, &crc32); - - build_id = elf_parser_get_build_id (elf); - -#if 0 - g_print (" debug link for %s is %s\n", filename, basename); -#endif - - if (!basename) - return NULL; - - dir = g_path_get_dirname (filename); - - /* Flatpak paths have the form of ".../files/bin" or ".../files/lib/x86_64-linux-gnu". */ - files = g_strrstr (dir, "/files/"); - if (files) - prefix = files + sizeof ("/files/") - 1; - - for (guint i = 0; debug_dirs[i]; i++) - { - /* Most files from Flatpak will be from .Platform, which usually has a prefix like "lib/x86_64-linux-gnu" - * but in the debug dir the files are under "usr/lib/x86_64-linux-gnu", so try with "usr" first. */ - g_autofree char *name = g_build_filename (debug_dirs[i], "usr", prefix, basename, NULL); - ElfParser *parser = elf_parser_new (name, NULL); - guint32 file_crc; - const char *file_build_id; - - if (!parser) - { - /* Files from Flatpak com.example.App.Debug have prefixes like "bin" or "lib", - * and they don't need the "usr" for the debug dir, so try without "usr". */ - g_free (name); - name = g_build_filename (debug_dirs[i], prefix, basename, NULL); - parser = elf_parser_new (name, NULL); - } - - if (parser) - { - /* If both files have build ids, and they don't match, - * there is no point computing a CRC32 that we know - * will fail - */ - file_build_id = elf_parser_get_build_id (parser); - if (build_id && file_build_id && strcmp (build_id, file_build_id) != 0) - goto skip; - - file_crc = elf_parser_get_crc32 (parser); - - if (file_crc == crc32) - { - result = parser; - *new_name = g_steal_pointer (&name); - break; - } - else - { - if (!already_warned (name)) - { - g_print ("warning: %s has wrong crc %x, %s has crc %x)\n", - name, file_crc, filename, crc32); - } - } - - skip: - elf_parser_free (parser); - } - } - - g_free (dir); - - return result; -} - -static GList * -get_debug_binaries (GList *files, - ElfParser *elf, - const char *filename, - const gchar * const *debug_dirs) -{ - ElfParser *build_id_file; - GHashTable *seen_names; - GList *free_us = NULL; - - build_id_file = get_build_id_file (elf, debug_dirs); - - if (build_id_file) - return g_list_prepend (files, build_id_file); - - /* .gnu_debuglink is actually a chain of debuglinks, and - * there have been real-world cases where following it was - * necessary to get useful debug information. - */ - seen_names = g_hash_table_new (g_str_hash, g_str_equal); - - while (elf) - { - char *debug_name; - - if (g_hash_table_lookup (seen_names, filename)) - break; - - g_hash_table_insert (seen_names, (char *)filename, (char *)filename); - - elf = get_debuglink_file (elf, filename, &debug_name, debug_dirs); - - if (elf) - { - files = g_list_prepend (files, elf); - free_us = g_list_prepend (free_us, debug_name); - filename = debug_name; - } - } - - g_list_foreach (free_us, (GFunc)g_free, NULL); - g_list_free (free_us); - - g_hash_table_destroy (seen_names); - - return files; -} - -#ifdef __linux__ -G_GNUC_PRINTF (1, 2) -static char ** -get_lines (const char *format, - ...) -{ - va_list args; - char *filename; - char **result = NULL; - char *contents; - - va_start (args, format); - filename = g_strdup_vprintf (format, args); - va_end (args); - - if (g_file_get_contents (filename, &contents, NULL, NULL)) - { - result = g_strsplit (contents, "\n", -1); - - g_free (contents); - } - - g_free (filename); - - return result; -} -#endif - -static const uint8_t * -get_vdso_bytes (size_t *length) -{ -#ifdef __linux__ - static const uint8_t *bytes = NULL; - static size_t n_bytes = 0; - static gboolean has_data; - - if (!has_data) - { - char **lines = get_lines ("/proc/%d/maps", getpid()); - int i; - - for (i = 0; lines[i] != NULL; ++i) - { - char file[256]; - gulong start; - gulong end; - int count = sscanf ( - lines[i], "%lx-%lx %*15s %*x %*x:%*x %*u %255s", - &start, &end, file); - - if (count == 3 && strcmp (file, "[vdso]") == 0) - { - n_bytes = end - start; - - /* Dup the memory here so that valgrind will only - * report one 1 byte invalid read instead of - * a ton when the elf parser scans the vdso - * - * The reason we get a spurious invalid read from - * valgrind is that we are getting the address directly - * from /proc/maps, and valgrind knows that its mmap() - * wrapper never returned that address. But since it - * is a legal mapping, it is legal to read it. - */ - bytes = g_memdup2 ((uint8_t *)start, n_bytes); - - has_data = TRUE; - } - } - } - - if (length) - *length = n_bytes; - - return bytes; -#else - if (length) - *length = 0; - return NULL; -#endif -} - -bin_file_t * -bin_file_new (const char *filename, - const gchar * const *debug_dirs) -{ - const gchar *real_filename = filename; - ElfParser *elf = NULL; - bin_file_t *bf; - - bf = g_new0 (bin_file_t, 1); - - bf->inode_check = FALSE; - bf->filename = g_strdup (filename); - bf->undefined_name = g_strdup_printf ("In file %s", real_filename); - bf->ref_count = 1; - bf->elf_files = NULL; - - if (strcmp (filename, "[vdso]") == 0) - { - const guint8 *vdso_bytes; - gsize length; - - vdso_bytes = get_vdso_bytes (&length); - - if (vdso_bytes) - elf = elf_parser_new_from_data (vdso_bytes, length); - } - else - { - elf = elf_parser_new (filename, NULL); - } - - if (elf) - { - /* We need the text offset of the actual binary, not the - * (potential) debug binaries - */ - bf->text_offset = elf_parser_get_text_offset (elf); - - bf->elf_files = get_debug_binaries (bf->elf_files, elf, filename, debug_dirs); - bf->elf_files = g_list_append (bf->elf_files, elf); - - bf->inode = read_inode (filename); - } - - return bf; -} - -void -bin_file_free (bin_file_t *bin_file) -{ - if (--bin_file->ref_count == 0) - { - g_list_foreach (bin_file->elf_files, (GFunc)elf_parser_free, NULL); - g_list_free (bin_file->elf_files); - - g_free (bin_file->filename); - g_free (bin_file->undefined_name); - g_free (bin_file); - } -} - -const bin_symbol_t * -bin_file_lookup_symbol (bin_file_t *bin_file, - gulong address) -{ - GList *list; - -#if 0 - g_print ("-=-=-=- \n"); - - g_print ("bin file lookup lookup %d\n", address); -#endif - - address -= bin_file->text_offset; - -#if 0 - g_print ("lookup %lx in %s\n", address, bin_file->filename); -#endif - - for (list = bin_file->elf_files; list != NULL; list = list->next) - { - ElfParser *elf = list->data; - const ElfSym *sym = elf_parser_lookup_symbol (elf, address); - - if (sym) - { -#if 0 - g_print ("found %lx => %s\n", address, - bin_symbol_get_name (bin_file, (const bin_symbol_t *)sym)); -#endif - return (const bin_symbol_t *)sym; - } - } - -#if 0 - g_print ("%lx undefined in %s (textoffset %lx)\n", - address + bin_file->text_offset, - bin_file->filename, - bin_file->text_offset); -#endif - - return (const bin_symbol_t *)bin_file->undefined_name; -} - -gboolean -bin_file_check_inode (bin_file_t *bin_file, - ino_t inode) -{ - if (bin_file->inode == inode) - return TRUE; - - if (!bin_file->elf_files) - return FALSE; - - if (!bin_file->inode_check) - { - g_print ("warning: Inode mismatch for %s (disk: %"G_GUINT64_FORMAT", memory: %"G_GUINT64_FORMAT")\n", - bin_file->filename, (guint64)bin_file->inode, (guint64)inode); - - bin_file->inode_check = TRUE; - } - - return FALSE; -} - -static const ElfSym * -get_elf_sym (bin_file_t *file, - const bin_symbol_t *symbol, - ElfParser **elf_ret) -{ - GList *list; - - for (list = file->elf_files; list != NULL; list = list->next) - { - const ElfSym *sym = (const ElfSym *)symbol; - ElfParser *elf = list->data; - - if (elf_parser_owns_symbol (elf, sym)) - { - *elf_ret = elf; - return sym; - } - } - - g_critical ("Internal error: unrecognized symbol pointer"); - - *elf_ret = NULL; - return NULL; -} - -const char * -bin_symbol_get_name (bin_file_t *file, - const bin_symbol_t *symbol) -{ - if (file->undefined_name == (char *)symbol) - { - return file->undefined_name; - } - else - { - ElfParser *elf; - const ElfSym *sym; - - sym = get_elf_sym (file, symbol, &elf); - - return elf_parser_get_sym_name (elf, sym); - } -} - -gulong -bin_symbol_get_address (bin_file_t *file, - const bin_symbol_t *symbol) -{ - if (file->undefined_name == (char *)symbol) - { - return 0x0; - } - else - { - ElfParser *elf; - const ElfSym *sym; - - sym = get_elf_sym (file, symbol, &elf); - - return elf_parser_get_sym_address (elf, sym); - } -} - -void -bin_symbol_get_address_range (bin_file_t *file, - const bin_symbol_t *symbol, - gulong *begin, - gulong *end) -{ - if (file->undefined_name == (char *)symbol) - { - *begin = 0; - *end = 0; - } - else - { - ElfParser *elf; - const ElfSym *sym; - - sym = get_elf_sym (file, symbol, &elf); - elf_parser_get_sym_address_range (elf, sym, begin, end); - } -} diff --git a/src/libsysprof/binfile.h b/src/libsysprof/binfile.h deleted file mode 100644 index 2feb1188..00000000 --- a/src/libsysprof/binfile.h +++ /dev/null @@ -1,51 +0,0 @@ -/* MemProf -- memory profiler and leak detector - * Copyright 1999, 2000, 2001, Red Hat, Inc. - * Copyright 2002, Kristian Rietveld - * - * Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef BIN_FILE_H -#define BIN_FILE_H - -#include -#include - -typedef struct bin_file_t bin_file_t; -typedef struct bin_symbol_t bin_symbol_t; - -/* Binary File */ - -bin_file_t *bin_file_new (const char *filename, - const gchar * const *debug_dirs); -void bin_file_free (bin_file_t *bin_file); -const bin_symbol_t *bin_file_lookup_symbol (bin_file_t *bin_file, - gulong address); -gboolean bin_file_check_inode (bin_file_t *bin_file, - ino_t inode); -const char *bin_symbol_get_name (bin_file_t *bin_file, - const bin_symbol_t *symbol); -gulong bin_symbol_get_address (bin_file_t *bin_file, - const bin_symbol_t *symbol); -void bin_symbol_get_address_range (bin_file_t *bin_file, - const bin_symbol_t *symbol, - gulong *begin, - gulong *end); - -#endif diff --git a/src/libsysprof/categories.txt b/src/libsysprof/categories.txt new file mode 100644 index 00000000..085e0b37 --- /dev/null +++ b/src/libsysprof/categories.txt @@ -0,0 +1,240 @@ +Cairo: +* paint inherit + +Pixman: +pixman* paint inherit + +EGL: +egl* paint inherit + + +FontConfig: +* layout inherit + + +GL: +gl* paint inherit + + +GLib: +g_main_* main-loop +g_wakeup_* main-loop inherit +g_timeout_* main-loop +g_idle_* main-loop +block_source main-loop inherit +unblock_source main-loop inherit +g_source_remove main-loop inherit +g_source_unref_internal main-loop inherit +g_source_iter_next main-loop inherit +g_source_ref main-loop inherit +g_source_unref main-loop inherit +g_source_destroy* main-loop inherit + +g_malloc* memory inherit +g_realloc* memory inherit +g_new* memory inherit +g_aligned_* memory inherit +g_free memory inherit +g_free_sized memory inherit + +g_io_channel_* io inherit +_g_socket_* io inherit +g_socket_* io inherit + +g_mutex* locking inherit +g_rec_mutex* locking inherit +g_bit_lock locking inherit +g_bit_trylock locking inherit +g_bit_unlock locking inherit +g_rw_lock* locking inherit +g_cond_wait locking inherit +g_cond_signal locking inherit +g_cond_broadcast locking inherit +g_pointer_bit* locking inherit +g_static_rw_lock* locking inherit +g_static_rec_mutex* locking inherit + + +GdkPixbuf: +* icons inherit + + +Gio: +gdbus* ipc inherit +g_dbus_* ipc inherit +g_bus_* ipc inherit +g_file_* io +g_local_file_* io +g_input_* io inherit +g_output_* io inherit +g_io_* io inherit +g_zlib_* io inherit + + +GTK 4: +gsk_gl_command_queue* graphics inherit + +gdk_cairo* paint inherit +gtk_widget_snapshot paint inherit +gtk_widget_real_snapshot paint inherit +gtk_widget_render paint inherit +gdk_snapshot_* paint inherit +gtk_snapshot_* paint inherit +_gsk_* paint inherit +gsk_* paint inherit +gdk_frame_clock_paint paint inherit +_gdk_frame_clock_emit_paint paint inherit +_gdk_frame_clock_emit_after_paint paint inherit +surface_render paint inherit + +_gtk_css_* css inherit +gtk_css_* css inherit + +gtk_widget_compute_* layout inherit +gtk_widget_measure layout inherit +_gdk_frame_clock_emit_layout layout inherit +gtk_builder_* layout inherit +gtk_buildable_* layout inherit +gtk_text_view_validate_onscreen layout inherit +find_by_log_attrs layout inherit +blink_cb layout inherit +gtk_layout_manager_* layout inherit +gtk_widget_query_size* layout inherit + +gtk_widget_do_pick input inherit +gdk_event_source_* input +gdk_surface_handle_event input inherit +gtk_im_* input inherit +do_update_im_spot_location input inherit +_gtk_gesture_* input inherit +gtk_gesture_* input inherit +_gtk_event_controller_* input inherit +gtk_event_controller_* input inherit +gtk_drop_* input inherit +gtk_pad_controller_* input inherit +gtk_shortcut_controller_* input inherit + +gtk_window_present* windowing inherit +gtk_widget_realize windowing inherit + +gtk_accessible_* a11y inherit +gtk_at_* a11y inherit + +gtk_action_observer_* actions inherit +gtk_action_helper_* actions inherit +gtk_action_muxer_* actions inherit +gtk_actionable_* actions inherit +gtk_menu_tracker_* actions inherit + +gtk_icon_* icons inherit +load_theme_thread icons inherit +load_icon_thread icons inherit + + +GtkSourceView: +update_syntax layout inherit +gtk_source_scheduler_dispatch layout inherit + + +Harfbuzz: +* layout inherit + + +JS: +* javascript inherit + + +libc: +poll main-loop inherit +select main-loop inherit +epoll_wait main-loop inherit + +read io inherit +write io inherit +close io inherit +pread io inherit +pwrite io inherit +preadv io inherit +pwritev io inherit +open io inherit +openat io inherit +recvmsg io inherit +recv io inherit +send io inherit +sendmsg io inherit +access io inherit +readlink io inherit +getdents64 io inherit +__read io inherit +__write io inherit + +pthread_mutex_lock* locking inherit +pthread_mutex_unlock* locking inherit +pthread_mutex_trylock* locking inherit +pthread_cond_wait* locking inherit +pthread_cond_signal* locking inherit + +__libc_calloc memory inherit +__libc_realloc memory inherit +__madvise memory inherit +aligned_alloc memory inherit +calloc memory inherit +cfree@* memory inherit +free memory inherit +madvise memory inherit +malloc memory inherit +memalign memory inherit +mmap memory inherit +mmap64 memory inherit +mprotect memory inherit +munmap memory inherit +posix_memalign memory inherit +realloc memory inherit +reallocarray memory inherit + +syscall kernel inherit +__syscall kernel inherit + +clock_gettime main-loop inherit +__clock_gettime main-loop inherit + + +libinput: +* input inherit + + +Mesa: +* graphics inherit + + +Mutter: +clutter_actor_paint paint inherit +clutter_paint_node_paint paint inherit + +cogl* graphics inherit + +clutter_events_pending main-loop inherit + + +Pango: +* layout inherit + + +rsvg: +* icons inherit + + +Wayland Client: +wl_* windowing inherit + + +Wayland Server: +wl_* windowing inherit + + +X11: +XPending main-loop inherit + + +Zstd: +* io inherit diff --git a/src/libsysprof/libsysprof.gresource.xml b/src/libsysprof/libsysprof.gresource.xml new file mode 100644 index 00000000..bdd76a46 --- /dev/null +++ b/src/libsysprof/libsysprof.gresource.xml @@ -0,0 +1,6 @@ + + + + categories.txt + + diff --git a/src/libsysprof/mapped-ring-buffer-source.h b/src/libsysprof/mapped-ring-buffer-source-private.h similarity index 53% rename from src/libsysprof/mapped-ring-buffer-source.h rename to src/libsysprof/mapped-ring-buffer-source-private.h index 167f4c84..1867e6ff 100644 --- a/src/libsysprof/mapped-ring-buffer-source.h +++ b/src/libsysprof/mapped-ring-buffer-source-private.h @@ -1,4 +1,4 @@ -/* mapped-ring-buffer-source.h +/* mapped-ring-buffer-source-private.h * * Copyright 2020 Christian Hergert * @@ -20,14 +20,8 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - #include -#include "sysprof-version-macros.h" - #include "mapped-ring-buffer.h" G_BEGIN_DECLS @@ -35,13 +29,13 @@ G_BEGIN_DECLS G_DEFINE_AUTOPTR_CLEANUP_FUNC (MappedRingBuffer, mapped_ring_buffer_unref) G_GNUC_INTERNAL -guint mapped_ring_buffer_create_source (MappedRingBuffer *self, - MappedRingBufferCallback callback, - gpointer user_data); +guint mapped_ring_buffer_create_source (MappedRingBuffer *self, + MappedRingBufferCallback callback, + gpointer user_data); G_GNUC_INTERNAL -guint mapped_ring_buffer_create_source_full (MappedRingBuffer *self, - MappedRingBufferCallback callback, - gpointer user_data, - GDestroyNotify destroy); +guint mapped_ring_buffer_create_source_full (MappedRingBuffer *self, + MappedRingBufferCallback callback, + gpointer user_data, + GDestroyNotify destroy); G_END_DECLS diff --git a/src/libsysprof/mapped-ring-buffer-source.c b/src/libsysprof/mapped-ring-buffer-source.c index fdeb4b77..a0a939e7 100644 --- a/src/libsysprof/mapped-ring-buffer-source.c +++ b/src/libsysprof/mapped-ring-buffer-source.c @@ -18,13 +18,11 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#define G_LOG_DOMAIN "sysprof-mapped-ring-buffer-source" - #include "config.h" #include -#include "mapped-ring-buffer-source.h" +#include "mapped-ring-buffer-source-private.h" typedef struct _MappedRingSource { diff --git a/src/libsysprof/meson.build b/src/libsysprof/meson.build index 42c5f0af..cfffe112 100644 --- a/src/libsysprof/meson.build +++ b/src/libsysprof/meson.build @@ -1,180 +1,213 @@ -libsysprof_c_args = [ '-DSYSPROF_COMPILATION' ] - libsysprof_public_sources = [ - 'sysprof-battery-source.c', - 'sysprof-callgraph-profile.c', - 'sysprof-capture-frame-object.c', - 'sysprof-capture-gobject.c', - 'sysprof-capture-model.c', - 'sysprof-capture-symbol-resolver.c', - 'sysprof-control-source.c', - 'sysprof-diskstat-source.c', - 'sysprof-elf-symbol-resolver.c', - 'sysprof-gjs-source.c', - 'sysprof-governor-source.c', - 'sysprof-hostinfo-source.c', - 'sysprof-jitmap-symbol-resolver.c', - 'sysprof-kernel-symbol.c', - 'sysprof-kernel-symbol-resolver.c', - 'sysprof-local-profiler.c', - 'sysprof-memprof-profile.c', - 'sysprof-memprof-source.c', - 'sysprof-netdev-source.c', - 'sysprof-preload-source.c', - 'sysprof-process-model.c', - 'sysprof-process-model-item.c', - 'sysprof-profile.c', + 'sysprof-battery-charge.c', + 'sysprof-bundled-symbolizer.c', + 'sysprof-callgraph-frame.c', + 'sysprof-callgraph-symbol.c', + 'sysprof-callgraph.c', + 'sysprof-callgraph-categorize.c', + 'sysprof-categories.c', + 'sysprof-category-summary.c', + 'sysprof-cpu-info.c', + 'sysprof-cpu-usage.c', + 'sysprof-diagnostic.c', + 'sysprof-disk-usage.c', + 'sysprof-document-allocation.c', + 'sysprof-document-counter-value.c', + 'sysprof-document-counter.c', + 'sysprof-document-ctrdef.c', + 'sysprof-document-ctrset.c', + 'sysprof-document-exit.c', + 'sysprof-document-file-chunk.c', + 'sysprof-document-file.c', + 'sysprof-document-fork.c', + 'sysprof-document-frame.c', + 'sysprof-document-jitmap.c', + 'sysprof-document-loader.c', + 'sysprof-document-log.c', + 'sysprof-document-mark.c', + 'sysprof-document-metadata.c', + 'sysprof-document-mmap.c', + 'sysprof-document-overlay.c', + 'sysprof-document-process.c', + 'sysprof-document-sample.c', + 'sysprof-document-traceable.c', + 'sysprof-document.c', + 'sysprof-elf-symbolizer.c', + 'sysprof-energy-usage.c', + 'sysprof-instrument.c', + 'sysprof-jitmap-symbolizer.c', + 'sysprof-kallsyms-symbolizer.c', + 'sysprof-malloc-tracing.c', + 'sysprof-mark-catalog.c', + 'sysprof-memory-usage.c', + 'sysprof-multi-symbolizer.c', + 'sysprof-network-usage.c', + 'sysprof-no-symbolizer.c', + 'sysprof-power-profile.c', 'sysprof-profiler.c', - 'sysprof-proxy-source.c', - 'sysprof-selection.c', - 'sysprof-source.c', + 'sysprof-proxied-instrument.c', + 'sysprof-recording.c', + 'sysprof-sampler.c', 'sysprof-spawnable.c', - 'sysprof-symbol-resolver.c', - 'sysprof-symbols-source.c', - 'sysprof-tracefd-source.c', + 'sysprof-symbol.c', + 'sysprof-symbolizer.c', + 'sysprof-symbols-bundle.c', + 'sysprof-system-logs.c', + 'sysprof-thread-info.c', + 'sysprof-time-span.c', + 'sysprof-tracer.c', ] libsysprof_public_headers = [ - 'sysprof-battery-source.h', - 'sysprof-callgraph-profile.h', - 'sysprof-capture-autocleanups.h', - 'sysprof-capture-frame-object.h', - 'sysprof-capture-gobject.h', - 'sysprof-capture-model.h', - 'sysprof-capture-symbol-resolver.h', - 'sysprof-control-source.h', - 'sysprof-diskstat-source.h', - 'sysprof-elf-symbol-resolver.h', - 'sysprof-gjs-source.h', - 'sysprof-governor-source.h', - 'sysprof-hostinfo-source.h', - 'sysprof-jitmap-symbol-resolver.h', - 'sysprof-netdev-source.h', - 'sysprof-kernel-symbol.h', - 'sysprof-kernel-symbol-resolver.h', - 'sysprof-local-profiler.h', - 'sysprof-memprof-profile.h', - 'sysprof-memprof-source.h', - 'sysprof-preload-source.h', - 'sysprof-process-model.h', - 'sysprof-process-model-item.h', - 'sysprof-profile.h', - 'sysprof-profiler.h', - 'sysprof-proxy-source.h', - 'sysprof-selection.h', - 'sysprof-source.h', - 'sysprof-spawnable.h', - 'sysprof-symbol-resolver.h', - 'sysprof-symbols-source.h', - 'sysprof-tracefd-source.h', 'sysprof.h', + 'sysprof-battery-charge.h', + 'sysprof-bundled-symbolizer.h', + 'sysprof-callgraph-frame.h', + 'sysprof-callgraph-symbol.h', + 'sysprof-callgraph.h', + 'sysprof-category-summary.h', + 'sysprof-cpu-info.h', + 'sysprof-cpu-usage.h', + 'sysprof-diagnostic.h', + 'sysprof-disk-usage.h', + 'sysprof-document-allocation.h', + 'sysprof-document-counter-value.h', + 'sysprof-document-counter.h', + 'sysprof-document-ctrdef.h', + 'sysprof-document-ctrset.h', + 'sysprof-document-exit.h', + 'sysprof-document-file-chunk.h', + 'sysprof-document-file.h', + 'sysprof-document-fork.h', + 'sysprof-document-frame.h', + 'sysprof-document-jitmap.h', + 'sysprof-document-loader.h', + 'sysprof-document-log.h', + 'sysprof-document-mark.h', + 'sysprof-document-metadata.h', + 'sysprof-document-mmap.h', + 'sysprof-document-overlay.h', + 'sysprof-document-process.h', + 'sysprof-document-sample.h', + 'sysprof-document-traceable.h', + 'sysprof-document.h', + 'sysprof-elf-symbolizer.h', + 'sysprof-energy-usage.h', + 'sysprof-instrument.h', + 'sysprof-jitmap-symbolizer.h', + 'sysprof-kallsyms-symbolizer.h', + 'sysprof-malloc-tracing.h', + 'sysprof-mark-catalog.h', + 'sysprof-memory-usage.h', + 'sysprof-mount.h', + 'sysprof-multi-symbolizer.h', + 'sysprof-network-usage.h', + 'sysprof-no-symbolizer.h', + 'sysprof-power-profile.h', + 'sysprof-profiler.h', + 'sysprof-proxied-instrument.h', + 'sysprof-recording.h', + 'sysprof-sampler.h', + 'sysprof-spawnable.h', + 'sysprof-symbol.h', + 'sysprof-symbolizer.h', + 'sysprof-symbols-bundle.h', + 'sysprof-system-logs.h', + 'sysprof-thread-info.h', + 'sysprof-time-span.h', + 'sysprof-tracer.h', ] libsysprof_private_sources = [ - 'binfile.c', - 'demangle.cpp', - 'elfparser.c', 'mapped-ring-buffer-source.c', - 'sysprof-flatpak.c', - 'sysprof-helpers.c', - 'sysprof-kallsyms.c', - 'sysprof-line-reader.c', - 'sysprof-map-lookaside.c', - 'sysprof-mountinfo.c', - 'sysprof-path-resolver.c', + 'sysprof-address-layout.c', + 'sysprof-controlfd-instrument.c', + 'sysprof-descendants-model.c', + 'sysprof-document-bitset-index.c', + 'sysprof-document-symbols.c', + 'sysprof-elf-loader.c', + 'sysprof-elf.c', + 'sysprof-journald-source.c', + 'sysprof-maps-parser.c', + 'sysprof-mount-device.c', + 'sysprof-mount-namespace.c', + 'sysprof-mount.c', + 'sysprof-perf-event-stream.c', 'sysprof-podman.c', - 'sysprof-polkit.c', - 'sysprof-symbol-map.c', - ipc_service_src, - stackstash_sources, - helpers_sources, + 'sysprof-process-info.c', + 'sysprof-strings.c', + 'sysprof-symbol-cache.c', ] -libsysprof_public_sources += libsysprof_capture_sources - -librax = static_library('rax', ['rax.c'], - c_args: [ '-Wno-declaration-after-statement', - '-Wno-format-nonliteral', - '-Wno-shadow' ], - gnu_symbol_visibility: 'hidden', -) - -librax_dep = declare_dependency( - link_whole: librax, - include_directories: include_directories('.'), -) - -polkit_dep = dependency('polkit-gobject-1', version: polkit_req_version, required: false) if polkit_dep.found() - libsysprof_c_args += ['-DHAVE_POLKIT'] + libsysprof_private_sources += ['sysprof-polkit.c'] endif -if dependency('polkit-gobject-1', version: '>= 0.114', required: false).found() - libsysprof_c_args += ['-DHAVE_POLKIT_AUTOPTR'] -endif - -# Subset of dependencies used in generating the pkg-config file -libsysprof_pkg_deps = [ - dependency('gio-2.0', version: glib_req_version), - dependency('gio-unix-2.0', version: glib_req_version), - dependency('json-glib-1.0'), - polkit_dep, - libsysprof_capture_deps, -] - if host_machine.system() == 'linux' - libsysprof_public_sources += [ - 'sysprof-memory-source.c', - 'sysprof-perf-counter.c', - 'sysprof-perf-source.c', - 'sysprof-proc-source.c', - ] - - libsysprof_public_headers += [ - 'sysprof-memory-source.h', - 'sysprof-perf-counter.h', - 'sysprof-perf-source.h', - 'sysprof-proc-source.h', - ] + libsysprof_private_sources += ['sysprof-linux-instrument.c'] endif -if host_machine.system() == 'darwin' - libsysprof_pkg_deps += [ dependency('libelf') ] - libsysprof_c_args += [ '-DNT_GNU_BUILD_ID=3', '-DELF_NOTE_GNU="GNU"', '-D__LIBELF_INTERNAL__' ] -endif +libsysprof_enum_headers = [ + 'sysprof-callgraph.h', +] -libsysprof_deps = libsysprof_pkg_deps +libsysprof_enums = gnome.mkenums_simple('sysprof-enums', + body_prefix: '#include "config.h"', + header_prefix: '#include "sysprof-version-macros.h"', + decorator: '_SYSPROF_EXTERN', + sources: libsysprof_enum_headers, + install_header: true, + install_dir: sysprof_header_dir, +) -libsysprof_libs_private = [] +libsysprof_resources = gnome.compile_resources('libsysprof-resources', 'libsysprof.gresource.xml', + source_dir: 'resources', + c_name: 'libsysprof', +) -if host_machine.system() != 'darwin' - libsysprof_deps += [cxx.find_library('stdc++')] - libsysprof_libs_private += '-lstdc++' -endif +libsysprof_deps = [ + dependency('gio-2.0', version: glib_req_version), + dependency('gio-unix-2.0', + version: glib_req_version, + required: host_machine.system() != 'windows'), + dependency('libdex-1', version: dex_req_version), + dependency('json-glib-1.0'), + + libsystemd_dep, + polkit_dep, + + libeggbitset_static_dep, + libelfparser_static_dep, + liblinereader_static_dep, + libsysprof_capture_dep, +] libsysprof_static = static_library( - 'sysprof', - libsysprof_public_sources + libsysprof_private_sources, + 'sysprof-@0@'.format(soname_major_version), + libsysprof_public_sources + + libsysprof_private_sources + + libsysprof_enums, + libsysprof_resources, include_directories: [include_directories('.'), - ipc_include_dirs, libsysprof_capture_include_dirs], dependencies: libsysprof_deps, - c_args: libsysprof_c_args, gnu_symbol_visibility: 'hidden', ) libsysprof_static_dep = declare_dependency( - link_whole: libsysprof_static, - dependencies: libsysprof_deps + [librax_dep], - include_directories: [include_directories('.'), libsysprof_capture_include_dirs], + link_with: libsysprof_static, + dependencies: libsysprof_deps, + include_directories: [include_directories('.'), + libsysprof_capture_include_dirs], ) -if get_option('libsysprof') -libsysprof = shared_library('sysprof-@0@'.format(libsysprof_api_version), - dependencies: libsysprof_deps + [libsysprof_static_dep], - install: true, - install_dir: get_option('libdir'), +libsysprof = library('sysprof-@0@'.format(soname_major_version), + dependencies: [libsysprof_static_dep], + gnu_symbol_visibility: 'hidden', + version: '@0@.0.0'.format(soname_major_version), + darwin_versions: '@0@.0'.format(soname_major_version), + install: get_option('libsysprof'), ) libsysprof_dep = declare_dependency( @@ -182,21 +215,18 @@ libsysprof_dep = declare_dependency( dependencies: libsysprof_deps, include_directories: [include_directories('.'), libsysprof_capture_include_dirs], ) -meson.override_dependency('sysprof-@0@'.format(libsysprof_api_version), libsysprof_dep) +meson.override_dependency('sysprof-@0@'.format(soname_major_version), libsysprof_dep) -pkgconfig.generate( - libsysprof, - subdirs: [ sysprof_header_subdir ], - description: 'The library for console applications embedding sysprof', - install_dir: join_paths(get_option('libdir'), 'pkgconfig'), - requires: [ 'gio-2.0' ], - libraries_private: [libsysprof_libs_private, libsysprof_pkg_deps], - variables: [ - 'datadir=' + datadir_for_pc_file, - ], +pkgconfig.generate(libsysprof, + subdirs: [sysprof_header_subdir], + description: 'A library for recording and analyzing system performance', + install_dir: join_paths(get_option('libdir'), 'pkgconfig'), + requires: ['gio-2.0'], + variables: ['datadir=' + datadir_for_pc_file], ) install_headers(libsysprof_public_headers, subdir: sysprof_header_subdir) -endif -subdir('preload') +if get_option('tests') + subdir('tests') +endif diff --git a/src/libsysprof/rax.c b/src/libsysprof/rax.c deleted file mode 100644 index 287f9855..00000000 --- a/src/libsysprof/rax.c +++ /dev/null @@ -1,1927 +0,0 @@ -/* Rax -- A radix tree implementation. - * - * Version 1.2 -- 7 February 2019 - * - * Copyright (c) 2017-2019, Salvatore Sanfilippo - * 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 Redis 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 -#include -#include -#include -#include -#include -#include "rax.h" - -#ifndef RAX_MALLOC_INCLUDE -#define RAX_MALLOC_INCLUDE "rax_malloc.h" -#endif - -#include RAX_MALLOC_INCLUDE - -/* This is a special pointer that is guaranteed to never have the same value - * of a radix tree node. It's used in order to report "not found" error without - * requiring the function to have multiple return values. */ -void *raxNotFound = (void*)"rax-not-found-pointer"; - -/* -------------------------------- Debugging ------------------------------ */ - -void raxDebugShowNode(const char *msg, raxNode *n); - -/* Turn debugging messages on/off by compiling with RAX_DEBUG_MSG macro on. - * When RAX_DEBUG_MSG is defined by default Rax operations will emit a lot - * of debugging info to the standard output, however you can still turn - * debugging on/off in order to enable it only when you suspect there is an - * operation causing a bug using the function raxSetDebugMsg(). */ -#ifdef RAX_DEBUG_MSG -#define debugf(...) \ - if (raxDebugMsg) { \ - printf("%s:%s:%d:\t", __FILE__, __func__, __LINE__); \ - printf(__VA_ARGS__); \ - fflush(stdout); \ - } - -#define debugnode(msg,n) raxDebugShowNode(msg,n) -#else -#define debugf(...) -#define debugnode(msg,n) -#endif - -/* By default log debug info if RAX_DEBUG_MSG is defined. */ -static int raxDebugMsg = 1; - -/* When debug messages are enabled, turn them on/off dynamically. By - * default they are enabled. Set the state to 0 to disable, and 1 to - * re-enable. */ -void raxSetDebugMsg(int onoff) { - raxDebugMsg = onoff; -} - -/* ------------------------- raxStack functions -------------------------- - * The raxStack is a simple stack of pointers that is capable of switching - * from using a stack-allocated array to dynamic heap once a given number of - * items are reached. It is used in order to retain the list of parent nodes - * while walking the radix tree in order to implement certain operations that - * need to navigate the tree upward. - * ------------------------------------------------------------------------- */ - -/* Initialize the stack. */ -static inline void raxStackInit(raxStack *ts) { - ts->stack = ts->static_items; - ts->items = 0; - ts->maxitems = RAX_STACK_STATIC_ITEMS; - ts->oom = 0; -} - -/* Push an item into the stack, returns 1 on success, 0 on out of memory. */ -static inline int raxStackPush(raxStack *ts, void *ptr) { - if (ts->items == ts->maxitems) { - if (ts->stack == ts->static_items) { - ts->stack = rax_malloc(sizeof(void*)*ts->maxitems*2); - if (ts->stack == NULL) { - ts->stack = ts->static_items; - ts->oom = 1; - errno = ENOMEM; - return 0; - } - memcpy(ts->stack,ts->static_items,sizeof(void*)*ts->maxitems); - } else { - void **newalloc = rax_realloc(ts->stack,sizeof(void*)*ts->maxitems*2); - if (newalloc == NULL) { - ts->oom = 1; - errno = ENOMEM; - return 0; - } - ts->stack = newalloc; - } - ts->maxitems *= 2; - } - ts->stack[ts->items] = ptr; - ts->items++; - return 1; -} - -/* Pop an item from the stack, the function returns NULL if there are no - * items to pop. */ -static inline void *raxStackPop(raxStack *ts) { - if (ts->items == 0) return NULL; - ts->items--; - return ts->stack[ts->items]; -} - -/* Return the stack item at the top of the stack without actually consuming - * it. */ -static inline void *raxStackPeek(raxStack *ts) { - if (ts->items == 0) return NULL; - return ts->stack[ts->items-1]; -} - -/* Free the stack in case we used heap allocation. */ -static inline void raxStackFree(raxStack *ts) { - if (ts->stack != ts->static_items) rax_free(ts->stack); -} - -/* ---------------------------------------------------------------------------- - * Radix tree implementation - * --------------------------------------------------------------------------*/ - -/* Return the padding needed in the characters section of a node having size - * 'nodesize'. The padding is needed to store the child pointers to aligned - * addresses. Note that we add 4 to the node size because the node has a four - * bytes header. */ -#define raxPadding(nodesize) ((sizeof(void*)-(((nodesize)+4) % sizeof(void*))) & (sizeof(void*)-1)) - -/* Return the pointer to the last child pointer in a node. For the compressed - * nodes this is the only child pointer. */ -#define raxNodeLastChildPtr(n) ((raxNode**) ( \ - ((char*)(n)) + \ - raxNodeCurrentLength(n) - \ - sizeof(raxNode*) - \ - (((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \ -)) - -/* Return the pointer to the first child pointer. */ -#define raxNodeFirstChildPtr(n) ((raxNode**) ( \ - (n)->data + \ - (n)->size + \ - raxPadding((n)->size))) - -/* Return the current total size of the node. Note that the second line - * computes the padding after the string of characters, needed in order to - * save pointers to aligned addresses. */ -#define raxNodeCurrentLength(n) ( \ - sizeof(raxNode)+(n)->size+ \ - raxPadding((n)->size)+ \ - ((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \ - (((n)->iskey && !(n)->isnull)*sizeof(void*)) \ -) - -/* Allocate a new non compressed node with the specified number of children. - * If datafield is true, the allocation is made large enough to hold the - * associated data pointer. - * Returns the new node pointer. On out of memory NULL is returned. */ -raxNode *raxNewNode(size_t children, int datafield) { - size_t nodesize = sizeof(raxNode)+children+raxPadding(children)+ - sizeof(raxNode*)*children; - if (datafield) nodesize += sizeof(void*); - raxNode *node = rax_malloc(nodesize); - if (node == NULL) return NULL; - node->iskey = 0; - node->isnull = 0; - node->iscompr = 0; - node->size = children; - return node; -} - -/* Allocate a new rax and return its pointer. On out of memory the function - * returns NULL. */ -rax *raxNew(void) { - rax *rax = rax_malloc(sizeof(*rax)); - if (rax == NULL) return NULL; - rax->numele = 0; - rax->numnodes = 1; - rax->head = raxNewNode(0,0); - if (rax->head == NULL) { - rax_free(rax); - return NULL; - } else { - return rax; - } -} - -/* realloc the node to make room for auxiliary data in order - * to store an item in that node. On out of memory NULL is returned. */ -raxNode *raxReallocForData(raxNode *n, void *data) { - if (data == NULL) return n; /* No reallocation needed, setting isnull=1 */ - size_t curlen = raxNodeCurrentLength(n); - return rax_realloc(n,curlen+sizeof(void*)); -} - -/* Set the node auxiliary data to the specified pointer. */ -void raxSetData(raxNode *n, void *data) { - n->iskey = 1; - if (data != NULL) { - n->isnull = 0; - void **ndata = (void**) - ((char*)n+raxNodeCurrentLength(n)-sizeof(void*)); - memcpy(ndata,&data,sizeof(data)); - } else { - n->isnull = 1; - } -} - -/* Get the node auxiliary data. */ -void *raxGetData(raxNode *n) { - if (n->isnull) return NULL; - void **ndata =(void**)((char*)n+raxNodeCurrentLength(n)-sizeof(void*)); - void *data; - memcpy(&data,ndata,sizeof(data)); - return data; -} - -/* Add a new child to the node 'n' representing the character 'c' and return - * its new pointer, as well as the child pointer by reference. Additionally - * '***parentlink' is populated with the raxNode pointer-to-pointer of where - * the new child was stored, which is useful for the caller to replace the - * child pointer if it gets reallocated. - * - * On success the new parent node pointer is returned (it may change because - * of the realloc, so the caller should discard 'n' and use the new value). - * On out of memory NULL is returned, and the old node is still valid. */ -raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode ***parentlink) { - assert(n->iscompr == 0); - - size_t curlen = raxNodeCurrentLength(n); - n->size++; - size_t newlen = raxNodeCurrentLength(n); - n->size--; /* For now restore the original size. We'll update it only on - success at the end. */ - - /* Alloc the new child we will link to 'n'. */ - raxNode *child = raxNewNode(0,0); - if (child == NULL) return NULL; - - /* Make space in the original node. */ - raxNode *newn = rax_realloc(n,newlen); - if (newn == NULL) { - rax_free(child); - return NULL; - } - n = newn; - - /* After the reallocation, we have up to 8/16 (depending on the system - * pointer size, and the required node padding) bytes at the end, that is, - * the additional char in the 'data' section, plus one pointer to the new - * child, plus the padding needed in order to store addresses into aligned - * locations. - * - * So if we start with the following node, having "abde" edges. - * - * Note: - * - We assume 4 bytes pointer for simplicity. - * - Each space below corresponds to one byte - * - * [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP| - * - * After the reallocation we need: 1 byte for the new edge character - * plus 4 bytes for a new child pointer (assuming 32 bit machine). - * However after adding 1 byte to the edge char, the header + the edge - * characters are no longer aligned, so we also need 3 bytes of padding. - * In total the reallocation will add 1+4+3 bytes = 8 bytes: - * - * (Blank bytes are represented by ".") - * - * [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|[....][....] - * - * Let's find where to insert the new child in order to make sure - * it is inserted in-place lexicographically. Assuming we are adding - * a child "c" in our case pos will be = 2 after the end of the following - * loop. */ - int pos; - for (pos = 0; pos < n->size; pos++) { - if (n->data[pos] > c) break; - } - - /* Now, if present, move auxiliary data pointer at the end - * so that we can mess with the other data without overwriting it. - * We will obtain something like that: - * - * [HDR*][abde][Aptr][Bptr][Dptr][Eptr][....][....]|AUXP| - */ - unsigned char *src, *dst; - if (n->iskey && !n->isnull) { - src = ((unsigned char*)n+curlen-sizeof(void*)); - dst = ((unsigned char*)n+newlen-sizeof(void*)); - memmove(dst,src,sizeof(void*)); - } - - /* Compute the "shift", that is, how many bytes we need to move the - * pointers section forward because of the addition of the new child - * byte in the string section. Note that if we had no padding, that - * would be always "1", since we are adding a single byte in the string - * section of the node (where now there is "abde" basically). - * - * However we have padding, so it could be zero, or up to 8. - * - * Another way to think at the shift is, how many bytes we need to - * move child pointers forward *other than* the obvious sizeof(void*) - * needed for the additional pointer itself. */ - size_t shift = newlen - curlen - sizeof(void*); - - /* We said we are adding a node with edge 'c'. The insertion - * point is between 'b' and 'd', so the 'pos' variable value is - * the index of the first child pointer that we need to move forward - * to make space for our new pointer. - * - * To start, move all the child pointers after the insertion point - * of shift+sizeof(pointer) bytes on the right, to obtain: - * - * [HDR*][abde][Aptr][Bptr][....][....][Dptr][Eptr]|AUXP| - */ - src = n->data+n->size+ - raxPadding(n->size)+ - sizeof(raxNode*)*pos; - memmove(src+shift+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos)); - - /* Move the pointers to the left of the insertion position as well. Often - * we don't need to do anything if there was already some padding to use. In - * that case the final destination of the pointers will be the same, however - * in our example there was no pre-existing padding, so we added one byte - * plus three bytes of padding. After the next memmove() things will look - * like that: - * - * [HDR*][abde][....][Aptr][Bptr][....][Dptr][Eptr]|AUXP| - */ - if (shift) { - src = (unsigned char*) raxNodeFirstChildPtr(n); - memmove(src+shift,src,sizeof(raxNode*)*pos); - } - - /* Now make the space for the additional char in the data section, - * but also move the pointers before the insertion point to the right - * by shift bytes, in order to obtain the following: - * - * [HDR*][ab.d][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP| - */ - src = n->data+pos; - memmove(src+1,src,n->size-pos); - - /* We can now set the character and its child node pointer to get: - * - * [HDR*][abcd][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP| - * [HDR*][abcd][e...][Aptr][Bptr][Cptr][Dptr][Eptr]|AUXP| - */ - n->data[pos] = c; - n->size++; - src = (unsigned char*) raxNodeFirstChildPtr(n); - raxNode **childfield = (raxNode**)(src+sizeof(raxNode*)*pos); - memcpy(childfield,&child,sizeof(child)); - *childptr = child; - *parentlink = childfield; - return n; -} - -/* Turn the node 'n', that must be a node without any children, into a - * compressed node representing a set of nodes linked one after the other - * and having exactly one child each. The node can be a key or not: this - * property and the associated value if any will be preserved. - * - * The function also returns a child node, since the last node of the - * compressed chain cannot be part of the chain: it has zero children while - * we can only compress inner nodes with exactly one child each. */ -raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **child) { - assert(n->size == 0 && n->iscompr == 0); - void *data = NULL; /* Initialized only to avoid warnings. */ - size_t newsize; - - debugf("Compress node: %.*s\n", (int)len,s); - - /* Allocate the child to link to this node. */ - *child = raxNewNode(0,0); - if (*child == NULL) return NULL; - - /* Make space in the parent node. */ - newsize = sizeof(raxNode)+len+raxPadding(len)+sizeof(raxNode*); - if (n->iskey) { - data = raxGetData(n); /* To restore it later. */ - if (!n->isnull) newsize += sizeof(void*); - } - raxNode *newn = rax_realloc(n,newsize); - if (newn == NULL) { - rax_free(*child); - return NULL; - } - n = newn; - - n->iscompr = 1; - n->size = len; - memcpy(n->data,s,len); - if (n->iskey) raxSetData(n,data); - raxNode **childfield = raxNodeLastChildPtr(n); - memcpy(childfield,child,sizeof(*child)); - return n; -} - -/* Low level function that walks the tree looking for the string - * 's' of 'len' bytes. The function returns the number of characters - * of the key that was possible to process: if the returned integer - * is the same as 'len', then it means that the node corresponding to the - * string was found (however it may not be a key in case the node->iskey is - * zero or if simply we stopped in the middle of a compressed node, so that - * 'splitpos' is non zero). - * - * Otherwise if the returned integer is not the same as 'len', there was an - * early stop during the tree walk because of a character mismatch. - * - * The node where the search ended (because the full string was processed - * or because there was an early stop) is returned by reference as - * '*stopnode' if the passed pointer is not NULL. This node link in the - * parent's node is returned as '*plink' if not NULL. Finally, if the - * search stopped in a compressed node, '*splitpos' returns the index - * inside the compressed node where the search ended. This is useful to - * know where to split the node for insertion. - * - * Note that when we stop in the middle of a compressed node with - * a perfect match, this function will return a length equal to the - * 'len' argument (all the key matched), and will return a *splitpos which is - * always positive (that will represent the index of the character immediately - * *after* the last match in the current compressed node). - * - * When instead we stop at a compressed node and *splitpos is zero, it - * means that the current node represents the key (that is, none of the - * compressed node characters are needed to represent the key, just all - * its parents nodes). */ -static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) { - raxNode *h = rax->head; - raxNode **parentlink = &rax->head; - - size_t i = 0; /* Position in the string. */ - size_t j = 0; /* Position in the node children (or bytes if compressed).*/ - while(h->size && i < len) { - debugnode("Lookup current node",h); - unsigned char *v = h->data; - - if (h->iscompr) { - for (j = 0; j < h->size && i < len; j++, i++) { - if (v[j] != s[i]) break; - } - if (j != h->size) break; - } else { - /* Even when h->size is large, linear scan provides good - * performances compared to other approaches that are in theory - * more sounding, like performing a binary search. */ - for (j = 0; j < h->size; j++) { - if (v[j] == s[i]) break; - } - if (j == h->size) break; - i++; - } - - if (ts) raxStackPush(ts,h); /* Save stack of parent nodes. */ - raxNode **children = raxNodeFirstChildPtr(h); - if (h->iscompr) j = 0; /* Compressed node only child is at index 0. */ - memcpy(&h,children+j,sizeof(h)); - parentlink = children+j; - j = 0; /* If the new node is non compressed and we do not - iterate again (since i == len) set the split - position to 0 to signal this node represents - the searched key. */ - } - debugnode("Lookup stop node is",h); - if (stopnode) *stopnode = h; - if (plink) *plink = parentlink; - if (splitpos && h->iscompr) *splitpos = j; - return i; -} - -/* Insert the element 's' of size 'len', setting as auxiliary data - * the pointer 'data'. If the element is already present, the associated - * data is updated (only if 'overwrite' is set to 1), and 0 is returned, - * otherwise the element is inserted and 1 is returned. On out of memory the - * function returns 0 as well but sets errno to ENOMEM, otherwise errno will - * be set to 0. - */ -int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old, int overwrite) { - size_t i; - int j = 0; /* Split position. If raxLowWalk() stops in a compressed - node, the index 'j' represents the char we stopped within the - compressed node, that is, the position where to split the - node for insertion. */ - raxNode *h, **parentlink; - - debugf("### Insert %.*s with value %p\n", (int)len, s, data); - i = raxLowWalk(rax,s,len,&h,&parentlink,&j,NULL); - - /* If i == len we walked following the whole string. If we are not - * in the middle of a compressed node, the string is either already - * inserted or this middle node is currently not a key, but can represent - * our key. We have just to reallocate the node and make space for the - * data pointer. */ - if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) { - debugf("### Insert: node representing key exists\n"); - /* Make space for the value pointer if needed. */ - if (!h->iskey || (h->isnull && overwrite)) { - h = raxReallocForData(h,data); - if (h) memcpy(parentlink,&h,sizeof(h)); - } - if (h == NULL) { - errno = ENOMEM; - return 0; - } - - /* Update the existing key if there is already one. */ - if (h->iskey) { - if (old) *old = raxGetData(h); - if (overwrite) raxSetData(h,data); - errno = 0; - return 0; /* Element already exists. */ - } - - /* Otherwise set the node as a key. Note that raxSetData() - * will set h->iskey. */ - raxSetData(h,data); - rax->numele++; - return 1; /* Element inserted. */ - } - - /* If the node we stopped at is a compressed node, we need to - * split it before to continue. - * - * Splitting a compressed node have a few possible cases. - * Imagine that the node 'h' we are currently at is a compressed - * node containing the string "ANNIBALE" (it means that it represents - * nodes A -> N -> N -> I -> B -> A -> L -> E with the only child - * pointer of this node pointing at the 'E' node, because remember that - * we have characters at the edges of the graph, not inside the nodes - * themselves. - * - * In order to show a real case imagine our node to also point to - * another compressed node, that finally points at the node without - * children, representing 'O': - * - * "ANNIBALE" -> "SCO" -> [] - * - * When inserting we may face the following cases. Note that all the cases - * require the insertion of a non compressed node with exactly two - * children, except for the last case which just requires splitting a - * compressed node. - * - * 1) Inserting "ANNIENTARE" - * - * |B| -> "ALE" -> "SCO" -> [] - * "ANNI" -> |-| - * |E| -> (... continue algo ...) "NTARE" -> [] - * - * 2) Inserting "ANNIBALI" - * - * |E| -> "SCO" -> [] - * "ANNIBAL" -> |-| - * |I| -> (... continue algo ...) [] - * - * 3) Inserting "AGO" (Like case 1, but set iscompr = 0 into original node) - * - * |N| -> "NIBALE" -> "SCO" -> [] - * |A| -> |-| - * |G| -> (... continue algo ...) |O| -> [] - * - * 4) Inserting "CIAO" - * - * |A| -> "NNIBALE" -> "SCO" -> [] - * |-| - * |C| -> (... continue algo ...) "IAO" -> [] - * - * 5) Inserting "ANNI" - * - * "ANNI" -> "BALE" -> "SCO" -> [] - * - * The final algorithm for insertion covering all the above cases is as - * follows. - * - * ============================= ALGO 1 ============================= - * - * For the above cases 1 to 4, that is, all cases where we stopped in - * the middle of a compressed node for a character mismatch, do: - * - * Let $SPLITPOS be the zero-based index at which, in the - * compressed node array of characters, we found the mismatching - * character. For example if the node contains "ANNIBALE" and we add - * "ANNIENTARE" the $SPLITPOS is 4, that is, the index at which the - * mismatching character is found. - * - * 1. Save the current compressed node $NEXT pointer (the pointer to the - * child element, that is always present in compressed nodes). - * - * 2. Create "split node" having as child the non common letter - * at the compressed node. The other non common letter (at the key) - * will be added later as we continue the normal insertion algorithm - * at step "6". - * - * 3a. IF $SPLITPOS == 0: - * Replace the old node with the split node, by copying the auxiliary - * data if any. Fix parent's reference. Free old node eventually - * (we still need its data for the next steps of the algorithm). - * - * 3b. IF $SPLITPOS != 0: - * Trim the compressed node (reallocating it as well) in order to - * contain $splitpos characters. Change child pointer in order to link - * to the split node. If new compressed node len is just 1, set - * iscompr to 0 (layout is the same). Fix parent's reference. - * - * 4a. IF the postfix len (the length of the remaining string of the - * original compressed node after the split character) is non zero, - * create a "postfix node". If the postfix node has just one character - * set iscompr to 0, otherwise iscompr to 1. Set the postfix node - * child pointer to $NEXT. - * - * 4b. IF the postfix len is zero, just use $NEXT as postfix pointer. - * - * 5. Set child[0] of split node to postfix node. - * - * 6. Set the split node as the current node, set current index at child[1] - * and continue insertion algorithm as usually. - * - * ============================= ALGO 2 ============================= - * - * For case 5, that is, if we stopped in the middle of a compressed - * node but no mismatch was found, do: - * - * Let $SPLITPOS be the zero-based index at which, in the - * compressed node array of characters, we stopped iterating because - * there were no more keys character to match. So in the example of - * the node "ANNIBALE", adding the string "ANNI", the $SPLITPOS is 4. - * - * 1. Save the current compressed node $NEXT pointer (the pointer to the - * child element, that is always present in compressed nodes). - * - * 2. Create a "postfix node" containing all the characters from $SPLITPOS - * to the end. Use $NEXT as the postfix node child pointer. - * If the postfix node length is 1, set iscompr to 0. - * Set the node as a key with the associated value of the new - * inserted key. - * - * 3. Trim the current node to contain the first $SPLITPOS characters. - * As usually if the new node length is just 1, set iscompr to 0. - * Take the iskey / associated value as it was in the original node. - * Fix the parent's reference. - * - * 4. Set the postfix node as the only child pointer of the trimmed - * node created at step 1. - */ - - /* ------------------------- ALGORITHM 1 --------------------------- */ - if (h->iscompr && i != len) { - debugf("ALGO 1: Stopped at compressed node %.*s (%p)\n", - h->size, h->data, (void*)h); - debugf("Still to insert: %.*s\n", (int)(len-i), s+i); - debugf("Splitting at %d: '%c'\n", j, ((char*)h->data)[j]); - debugf("Other (key) letter is '%c'\n", s[i]); - - /* 1: Save next pointer. */ - raxNode **childfield = raxNodeLastChildPtr(h); - raxNode *next; - memcpy(&next,childfield,sizeof(next)); - debugf("Next is %p\n", (void*)next); - debugf("iskey %d\n", h->iskey); - if (h->iskey) { - debugf("key value is %p\n", raxGetData(h)); - } - - /* Set the length of the additional nodes we will need. */ - size_t trimmedlen = j; - size_t postfixlen = h->size - j - 1; - int split_node_is_key = !trimmedlen && h->iskey && !h->isnull; - size_t nodesize; - - /* 2: Create the split node. Also allocate the other nodes we'll need - * ASAP, so that it will be simpler to handle OOM. */ - raxNode *splitnode = raxNewNode(1, split_node_is_key); - raxNode *trimmed = NULL; - raxNode *postfix = NULL; - - if (trimmedlen) { - nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+ - sizeof(raxNode*); - if (h->iskey && !h->isnull) nodesize += sizeof(void*); - trimmed = rax_malloc(nodesize); - } - - if (postfixlen) { - nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+ - sizeof(raxNode*); - postfix = rax_malloc(nodesize); - } - - /* OOM? Abort now that the tree is untouched. */ - if (splitnode == NULL || - (trimmedlen && trimmed == NULL) || - (postfixlen && postfix == NULL)) - { - rax_free(splitnode); - rax_free(trimmed); - rax_free(postfix); - errno = ENOMEM; - return 0; - } - splitnode->data[0] = h->data[j]; - - if (j == 0) { - /* 3a: Replace the old node with the split node. */ - if (h->iskey) { - void *ndata = raxGetData(h); - raxSetData(splitnode,ndata); - } - memcpy(parentlink,&splitnode,sizeof(splitnode)); - } else { - /* 3b: Trim the compressed node. */ - trimmed->size = j; - memcpy(trimmed->data,h->data,j); - trimmed->iscompr = j > 1 ? 1 : 0; - trimmed->iskey = h->iskey; - trimmed->isnull = h->isnull; - if (h->iskey && !h->isnull) { - void *ndata = raxGetData(h); - raxSetData(trimmed,ndata); - } - raxNode **cp = raxNodeLastChildPtr(trimmed); - memcpy(cp,&splitnode,sizeof(splitnode)); - memcpy(parentlink,&trimmed,sizeof(trimmed)); - parentlink = cp; /* Set parentlink to splitnode parent. */ - rax->numnodes++; - } - - /* 4: Create the postfix node: what remains of the original - * compressed node after the split. */ - if (postfixlen) { - /* 4a: create a postfix node. */ - postfix->iskey = 0; - postfix->isnull = 0; - postfix->size = postfixlen; - postfix->iscompr = postfixlen > 1; - memcpy(postfix->data,h->data+j+1,postfixlen); - raxNode **cp = raxNodeLastChildPtr(postfix); - memcpy(cp,&next,sizeof(next)); - rax->numnodes++; - } else { - /* 4b: just use next as postfix node. */ - postfix = next; - } - - /* 5: Set splitnode first child as the postfix node. */ - raxNode **splitchild = raxNodeLastChildPtr(splitnode); - memcpy(splitchild,&postfix,sizeof(postfix)); - - /* 6. Continue insertion: this will cause the splitnode to - * get a new child (the non common character at the currently - * inserted key). */ - rax_free(h); - h = splitnode; - } else if (h->iscompr && i == len) { - /* ------------------------- ALGORITHM 2 --------------------------- */ - debugf("ALGO 2: Stopped at compressed node %.*s (%p) j = %d\n", - h->size, h->data, (void*)h, j); - - /* Allocate postfix & trimmed nodes ASAP to fail for OOM gracefully. */ - size_t postfixlen = h->size - j; - size_t nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+ - sizeof(raxNode*); - if (data != NULL) nodesize += sizeof(void*); - raxNode *postfix = rax_malloc(nodesize); - - nodesize = sizeof(raxNode)+j+raxPadding(j)+sizeof(raxNode*); - if (h->iskey && !h->isnull) nodesize += sizeof(void*); - raxNode *trimmed = rax_malloc(nodesize); - - if (postfix == NULL || trimmed == NULL) { - rax_free(postfix); - rax_free(trimmed); - errno = ENOMEM; - return 0; - } - - /* 1: Save next pointer. */ - raxNode **childfield = raxNodeLastChildPtr(h); - raxNode *next; - memcpy(&next,childfield,sizeof(next)); - - /* 2: Create the postfix node. */ - postfix->size = postfixlen; - postfix->iscompr = postfixlen > 1; - postfix->iskey = 1; - postfix->isnull = 0; - memcpy(postfix->data,h->data+j,postfixlen); - raxSetData(postfix,data); - raxNode **cp = raxNodeLastChildPtr(postfix); - memcpy(cp,&next,sizeof(next)); - rax->numnodes++; - - /* 3: Trim the compressed node. */ - trimmed->size = j; - trimmed->iscompr = j > 1; - trimmed->iskey = 0; - trimmed->isnull = 0; - memcpy(trimmed->data,h->data,j); - memcpy(parentlink,&trimmed,sizeof(trimmed)); - if (h->iskey) { - void *aux = raxGetData(h); - raxSetData(trimmed,aux); - } - - /* Fix the trimmed node child pointer to point to - * the postfix node. */ - cp = raxNodeLastChildPtr(trimmed); - memcpy(cp,&postfix,sizeof(postfix)); - - /* Finish! We don't need to continue with the insertion - * algorithm for ALGO 2. The key is already inserted. */ - rax->numele++; - rax_free(h); - return 1; /* Key inserted. */ - } - - /* We walked the radix tree as far as we could, but still there are left - * chars in our string. We need to insert the missing nodes. */ - while(i < len) { - raxNode *child; - - /* If this node is going to have a single child, and there - * are other characters, so that that would result in a chain - * of single-childed nodes, turn it into a compressed node. */ - if (h->size == 0 && len-i > 1) { - debugf("Inserting compressed node\n"); - size_t comprsize = len-i; - if (comprsize > RAX_NODE_MAX_SIZE) - comprsize = RAX_NODE_MAX_SIZE; - raxNode *newh = raxCompressNode(h,s+i,comprsize,&child); - if (newh == NULL) goto oom; - h = newh; - memcpy(parentlink,&h,sizeof(h)); - parentlink = raxNodeLastChildPtr(h); - i += comprsize; - } else { - debugf("Inserting normal node\n"); - raxNode **new_parentlink; - raxNode *newh = raxAddChild(h,s[i],&child,&new_parentlink); - if (newh == NULL) goto oom; - h = newh; - memcpy(parentlink,&h,sizeof(h)); - parentlink = new_parentlink; - i++; - } - rax->numnodes++; - h = child; - } - raxNode *newh = raxReallocForData(h,data); - if (newh == NULL) goto oom; - h = newh; - if (!h->iskey) rax->numele++; - raxSetData(h,data); - memcpy(parentlink,&h,sizeof(h)); - return 1; /* Element inserted. */ - -oom: - /* This code path handles out of memory after part of the sub-tree was - * already modified. Set the node as a key, and then remove it. However we - * do that only if the node is a terminal node, otherwise if the OOM - * happened reallocating a node in the middle, we don't need to free - * anything. */ - if (h->size == 0) { - h->isnull = 1; - h->iskey = 1; - rax->numele++; /* Compensate the next remove. */ - assert(raxRemove(rax,s,i,NULL) != 0); - } - errno = ENOMEM; - return 0; -} - -/* Overwriting insert. Just a wrapper for raxGenericInsert() that will - * update the element if there is already one for the same key. */ -int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { - return raxGenericInsert(rax,s,len,data,old,1); -} - -/* Non overwriting insert function: if an element with the same key - * exists, the value is not updated and the function returns 0. - * This is just a wrapper for raxGenericInsert(). */ -int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { - return raxGenericInsert(rax,s,len,data,old,0); -} - -/* Find a key in the rax, returns raxNotFound special void pointer value - * if the item was not found, otherwise the value associated with the - * item is returned. */ -void *raxFind(rax *rax, unsigned char *s, size_t len) { - raxNode *h; - - debugf("### Lookup: %.*s\n", (int)len, s); - int splitpos = 0; - size_t i = raxLowWalk(rax,s,len,&h,NULL,&splitpos,NULL); - if (i != len || (h->iscompr && splitpos != 0) || !h->iskey) - return raxNotFound; - return raxGetData(h); -} - -/* Return the memory address where the 'parent' node stores the specified - * 'child' pointer, so that the caller can update the pointer with another - * one if needed. The function assumes it will find a match, otherwise the - * operation is an undefined behavior (it will continue scanning the - * memory without any bound checking). */ -raxNode **raxFindParentLink(raxNode *parent, raxNode *child) { - raxNode **cp = raxNodeFirstChildPtr(parent); - raxNode *c; - while(1) { - memcpy(&c,cp,sizeof(c)); - if (c == child) break; - cp++; - } - return cp; -} - -/* Low level child removal from node. The new node pointer (after the child - * removal) is returned. Note that this function does not fix the pointer - * of the parent node in its parent, so this task is up to the caller. - * The function never fails for out of memory. */ -raxNode *raxRemoveChild(raxNode *parent, raxNode *child) { - debugnode("raxRemoveChild before", parent); - /* If parent is a compressed node (having a single child, as for definition - * of the data structure), the removal of the child consists into turning - * it into a normal node without children. */ - if (parent->iscompr) { - void *data = NULL; - if (parent->iskey) data = raxGetData(parent); - parent->isnull = 0; - parent->iscompr = 0; - parent->size = 0; - if (parent->iskey) raxSetData(parent,data); - debugnode("raxRemoveChild after", parent); - return parent; - } - - /* Otherwise we need to scan for the child pointer and memmove() - * accordingly. - * - * 1. To start we seek the first element in both the children - * pointers and edge bytes in the node. */ - raxNode **cp = raxNodeFirstChildPtr(parent); - raxNode **c = cp; - unsigned char *e = parent->data; - - /* 2. Search the child pointer to remove inside the array of children - * pointers. */ - while(1) { - raxNode *aux; - memcpy(&aux,c,sizeof(aux)); - if (aux == child) break; - c++; - e++; - } - - /* 3. Remove the edge and the pointer by memmoving the remaining children - * pointer and edge bytes one position before. */ - int taillen = parent->size - (e - parent->data) - 1; - debugf("raxRemoveChild tail len: %d\n", taillen); - memmove(e,e+1,taillen); - - /* Compute the shift, that is the amount of bytes we should move our - * child pointers to the left, since the removal of one edge character - * and the corresponding padding change, may change the layout. - * We just check if in the old version of the node there was at the - * end just a single byte and all padding: in that case removing one char - * will remove a whole sizeof(void*) word. */ - size_t shift = ((parent->size+4) % sizeof(void*)) == 1 ? sizeof(void*) : 0; - - /* Move the children pointers before the deletion point. */ - if (shift) - memmove(((char*)cp)-shift,cp,(parent->size-taillen-1)*sizeof(raxNode**)); - - /* Move the remaining "tail" pointers at the right position as well. */ - size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0; - memmove(((char*)c)-shift,c+1,taillen*sizeof(raxNode**)+valuelen); - - /* 4. Update size. */ - parent->size--; - - /* realloc the node according to the theoretical memory usage, to free - * data if we are over-allocating right now. */ - raxNode *newnode = rax_realloc(parent,raxNodeCurrentLength(parent)); - if (newnode) { - debugnode("raxRemoveChild after", newnode); - } - /* Note: if rax_realloc() fails we just return the old address, which - * is valid. */ - return newnode ? newnode : parent; -} - -/* Remove the specified item. Returns 1 if the item was found and - * deleted, 0 otherwise. */ -int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) { - raxNode *h; - raxStack ts; - - debugf("### Delete: %.*s\n", (int)len, s); - raxStackInit(&ts); - int splitpos = 0; - size_t i = raxLowWalk(rax,s,len,&h,NULL,&splitpos,&ts); - if (i != len || (h->iscompr && splitpos != 0) || !h->iskey) { - raxStackFree(&ts); - return 0; - } - if (old) *old = raxGetData(h); - h->iskey = 0; - rax->numele--; - - /* If this node has no children, the deletion needs to reclaim the - * no longer used nodes. This is an iterative process that needs to - * walk the three upward, deleting all the nodes with just one child - * that are not keys, until the head of the rax is reached or the first - * node with more than one child is found. */ - - int trycompress = 0; /* Will be set to 1 if we should try to optimize the - tree resulting from the deletion. */ - - if (h->size == 0) { - debugf("Key deleted in node without children. Cleanup needed.\n"); - raxNode *child = NULL; - while(h != rax->head) { - child = h; - debugf("Freeing child %p [%.*s] key:%d\n", (void*)child, - (int)child->size, (char*)child->data, child->iskey); - rax_free(child); - rax->numnodes--; - h = raxStackPop(&ts); - /* If this node has more then one child, or actually holds - * a key, stop here. */ - if (h->iskey || (!h->iscompr && h->size != 1)) break; - } - if (child) { - debugf("Unlinking child %p from parent %p\n", - (void*)child, (void*)h); - raxNode *new = raxRemoveChild(h,child); - if (new != h) { - raxNode *parent = raxStackPeek(&ts); - raxNode **parentlink; - if (parent == NULL) { - parentlink = &rax->head; - } else { - parentlink = raxFindParentLink(parent,h); - } - memcpy(parentlink,&new,sizeof(new)); - } - - /* If after the removal the node has just a single child - * and is not a key, we need to try to compress it. */ - if (new->size == 1 && new->iskey == 0) { - trycompress = 1; - h = new; - } - } - } else if (h->size == 1) { - /* If the node had just one child, after the removal of the key - * further compression with adjacent nodes is potentially possible. */ - trycompress = 1; - } - - /* Don't try node compression if our nodes pointers stack is not - * complete because of OOM while executing raxLowWalk() */ - if (trycompress && ts.oom) trycompress = 0; - - /* Recompression: if trycompress is true, 'h' points to a radix tree node - * that changed in a way that could allow to compress nodes in this - * sub-branch. Compressed nodes represent chains of nodes that are not - * keys and have a single child, so there are two deletion events that - * may alter the tree so that further compression is needed: - * - * 1) A node with a single child was a key and now no longer is a key. - * 2) A node with two children now has just one child. - * - * We try to navigate upward till there are other nodes that can be - * compressed, when we reach the upper node which is not a key and has - * a single child, we scan the chain of children to collect the - * compressible part of the tree, and replace the current node with the - * new one, fixing the child pointer to reference the first non - * compressible node. - * - * Example of case "1". A tree stores the keys "FOO" = 1 and - * "FOOBAR" = 2: - * - * - * "FOO" -> "BAR" -> [] (2) - * (1) - * - * After the removal of "FOO" the tree can be compressed as: - * - * "FOOBAR" -> [] (2) - * - * - * Example of case "2". A tree stores the keys "FOOBAR" = 1 and - * "FOOTER" = 2: - * - * |B| -> "AR" -> [] (1) - * "FOO" -> |-| - * |T| -> "ER" -> [] (2) - * - * After the removal of "FOOTER" the resulting tree is: - * - * "FOO" -> |B| -> "AR" -> [] (1) - * - * That can be compressed into: - * - * "FOOBAR" -> [] (1) - */ - if (trycompress) { - debugf("After removing %.*s:\n", (int)len, s); - debugnode("Compression may be needed",h); - debugf("Seek start node\n"); - - /* Try to reach the upper node that is compressible. - * At the end of the loop 'h' will point to the first node we - * can try to compress and 'parent' to its parent. */ - raxNode *parent; - while(1) { - parent = raxStackPop(&ts); - if (!parent || parent->iskey || - (!parent->iscompr && parent->size != 1)) break; - h = parent; - debugnode("Going up to",h); - } - raxNode *start = h; /* Compression starting node. */ - - /* Scan chain of nodes we can compress. */ - size_t comprsize = h->size; - int nodes = 1; - while(h->size != 0) { - raxNode **cp = raxNodeLastChildPtr(h); - memcpy(&h,cp,sizeof(h)); - if (h->iskey || (!h->iscompr && h->size != 1)) break; - /* Stop here if going to the next node would result into - * a compressed node larger than h->size can hold. */ - if (comprsize + h->size > RAX_NODE_MAX_SIZE) break; - nodes++; - comprsize += h->size; - } - if (nodes > 1) { - /* If we can compress, create the new node and populate it. */ - size_t nodesize = - sizeof(raxNode)+comprsize+raxPadding(comprsize)+sizeof(raxNode*); - raxNode *new = rax_malloc(nodesize); - /* An out of memory here just means we cannot optimize this - * node, but the tree is left in a consistent state. */ - if (new == NULL) { - raxStackFree(&ts); - return 1; - } - new->iskey = 0; - new->isnull = 0; - new->iscompr = 1; - new->size = comprsize; - rax->numnodes++; - - /* Scan again, this time to populate the new node content and - * to fix the new node child pointer. At the same time we free - * all the nodes that we'll no longer use. */ - comprsize = 0; - h = start; - while(h->size != 0) { - memcpy(new->data+comprsize,h->data,h->size); - comprsize += h->size; - raxNode **cp = raxNodeLastChildPtr(h); - raxNode *tofree = h; - memcpy(&h,cp,sizeof(h)); - rax_free(tofree); rax->numnodes--; - if (h->iskey || (!h->iscompr && h->size != 1)) break; - } - debugnode("New node",new); - - /* Now 'h' points to the first node that we still need to use, - * so our new node child pointer will point to it. */ - raxNode **cp = raxNodeLastChildPtr(new); - memcpy(cp,&h,sizeof(h)); - - /* Fix parent link. */ - if (parent) { - raxNode **parentlink = raxFindParentLink(parent,start); - memcpy(parentlink,&new,sizeof(new)); - } else { - rax->head = new; - } - - debugf("Compressed %d nodes, %d total bytes\n", - nodes, (int)comprsize); - } - } - raxStackFree(&ts); - return 1; -} - -/* This is the core of raxFree(): performs a depth-first scan of the - * tree and releases all the nodes found. */ -void raxRecursiveFree(rax *rax, raxNode *n, void (*free_callback)(void*)) { - debugnode("free traversing",n); - int numchildren = n->iscompr ? 1 : n->size; - raxNode **cp = raxNodeLastChildPtr(n); - while(numchildren--) { - raxNode *child; - memcpy(&child,cp,sizeof(child)); - raxRecursiveFree(rax,child,free_callback); - cp--; - } - debugnode("free depth-first",n); - if (free_callback && n->iskey && !n->isnull) - free_callback(raxGetData(n)); - rax_free(n); - rax->numnodes--; -} - -/* Free a whole radix tree, calling the specified callback in order to - * free the auxiliary data. */ -void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)) { - raxRecursiveFree(rax,rax->head,free_callback); - assert(rax->numnodes == 0); - rax_free(rax); -} - -/* Free a whole radix tree. */ -void raxFree(rax *rax) { - raxFreeWithCallback(rax,NULL); -} - -/* ------------------------------- Iterator --------------------------------- */ - -/* Initialize a Rax iterator. This call should be performed a single time - * to initialize the iterator, and must be followed by a raxSeek() call, - * otherwise the raxPrev()/raxNext() functions will just return EOF. */ -void raxStart(raxIterator *it, rax *rt) { - it->flags = RAX_ITER_EOF; /* No crash if the iterator is not seeked. */ - it->rt = rt; - it->key_len = 0; - it->key = it->key_static_string; - it->key_max = RAX_ITER_STATIC_LEN; - it->data = NULL; - it->node_cb = NULL; - raxStackInit(&it->stack); -} - -/* Append characters at the current key string of the iterator 'it'. This - * is a low level function used to implement the iterator, not callable by - * the user. Returns 0 on out of memory, otherwise 1 is returned. */ -int raxIteratorAddChars(raxIterator *it, unsigned char *s, size_t len) { - if (len == 0) return 1; - if (it->key_max < it->key_len+len) { - unsigned char *old = (it->key == it->key_static_string) ? NULL : - it->key; - size_t new_max = (it->key_len+len)*2; - it->key = rax_realloc(old,new_max); - if (it->key == NULL) { - it->key = (!old) ? it->key_static_string : old; - errno = ENOMEM; - return 0; - } - if (old == NULL) memcpy(it->key,it->key_static_string,it->key_len); - it->key_max = new_max; - } - /* Use memmove since there could be an overlap between 's' and - * it->key when we use the current key in order to re-seek. */ - memmove(it->key+it->key_len,s,len); - it->key_len += len; - return 1; -} - -/* Remove the specified number of chars from the right of the current - * iterator key. */ -void raxIteratorDelChars(raxIterator *it, size_t count) { - it->key_len -= count; -} - -/* Do an iteration step towards the next element. At the end of the step the - * iterator key will represent the (new) current key. If it is not possible - * to step in the specified direction since there are no longer elements, the - * iterator is flagged with RAX_ITER_EOF. - * - * If 'noup' is true the function starts directly scanning for the next - * lexicographically smaller children, and the current node is already assumed - * to be the parent of the last key node, so the first operation to go back to - * the parent will be skipped. This option is used by raxSeek() when - * implementing seeking a non existing element with the ">" or "<" options: - * the starting node is not a key in that particular case, so we start the scan - * from a node that does not represent the key set. - * - * The function returns 1 on success or 0 on out of memory. */ -int raxIteratorNextStep(raxIterator *it, int noup) { - if (it->flags & RAX_ITER_EOF) { - return 1; - } else if (it->flags & RAX_ITER_JUST_SEEKED) { - it->flags &= ~RAX_ITER_JUST_SEEKED; - return 1; - } - - /* Save key len, stack items and the node where we are currently - * so that on iterator EOF we can restore the current key and state. */ - size_t orig_key_len = it->key_len; - size_t orig_stack_items = it->stack.items; - raxNode *orig_node = it->node; - - while(1) { - int children = it->node->iscompr ? 1 : it->node->size; - if (!noup && children) { - debugf("GO DEEPER\n"); - /* Seek the lexicographically smaller key in this subtree, which - * is the first one found always going towards the first child - * of every successive node. */ - if (!raxStackPush(&it->stack,it->node)) return 0; - raxNode **cp = raxNodeFirstChildPtr(it->node); - if (!raxIteratorAddChars(it,it->node->data, - it->node->iscompr ? it->node->size : 1)) return 0; - memcpy(&it->node,cp,sizeof(it->node)); - /* Call the node callback if any, and replace the node pointer - * if the callback returns true. */ - if (it->node_cb && it->node_cb(&it->node)) - memcpy(cp,&it->node,sizeof(it->node)); - /* For "next" step, stop every time we find a key along the - * way, since the key is lexicographically smaller compared to - * what follows in the sub-children. */ - if (it->node->iskey) { - it->data = raxGetData(it->node); - return 1; - } - } else { - /* If we finished exploring the previous sub-tree, switch to the - * new one: go upper until a node is found where there are - * children representing keys lexicographically greater than the - * current key. */ - while(1) { - int old_noup = noup; - - /* Already on head? Can't go up, iteration finished. */ - if (!noup && it->node == it->rt->head) { - it->flags |= RAX_ITER_EOF; - it->stack.items = orig_stack_items; - it->key_len = orig_key_len; - it->node = orig_node; - return 1; - } - /* If there are no children at the current node, try parent's - * next child. */ - unsigned char prevchild = it->key[it->key_len-1]; - if (!noup) { - it->node = raxStackPop(&it->stack); - } else { - noup = 0; - } - /* Adjust the current key to represent the node we are - * at. */ - int todel = it->node->iscompr ? it->node->size : 1; - raxIteratorDelChars(it,todel); - - /* Try visiting the next child if there was at least one - * additional child. */ - if (!it->node->iscompr && it->node->size > (old_noup ? 0 : 1)) { - raxNode **cp = raxNodeFirstChildPtr(it->node); - int i = 0; - while (i < it->node->size) { - debugf("SCAN NEXT %c\n", it->node->data[i]); - if (it->node->data[i] > prevchild) break; - i++; - cp++; - } - if (i != it->node->size) { - debugf("SCAN found a new node\n"); - raxIteratorAddChars(it,it->node->data+i,1); - if (!raxStackPush(&it->stack,it->node)) return 0; - memcpy(&it->node,cp,sizeof(it->node)); - /* Call the node callback if any, and replace the node - * pointer if the callback returns true. */ - if (it->node_cb && it->node_cb(&it->node)) - memcpy(cp,&it->node,sizeof(it->node)); - if (it->node->iskey) { - it->data = raxGetData(it->node); - return 1; - } - break; - } - } - } - } - } -} - -/* Seek the greatest key in the subtree at the current node. Return 0 on - * out of memory, otherwise 1. This is a helper function for different - * iteration functions below. */ -int raxSeekGreatest(raxIterator *it) { - while(it->node->size) { - if (it->node->iscompr) { - if (!raxIteratorAddChars(it,it->node->data, - it->node->size)) return 0; - } else { - if (!raxIteratorAddChars(it,it->node->data+it->node->size-1,1)) - return 0; - } - raxNode **cp = raxNodeLastChildPtr(it->node); - if (!raxStackPush(&it->stack,it->node)) return 0; - memcpy(&it->node,cp,sizeof(it->node)); - } - return 1; -} - -/* Like raxIteratorNextStep() but implements an iteration step moving - * to the lexicographically previous element. The 'noup' option has a similar - * effect to the one of raxIteratorNextStep(). */ -int raxIteratorPrevStep(raxIterator *it, int noup) { - if (it->flags & RAX_ITER_EOF) { - return 1; - } else if (it->flags & RAX_ITER_JUST_SEEKED) { - it->flags &= ~RAX_ITER_JUST_SEEKED; - return 1; - } - - /* Save key len, stack items and the node where we are currently - * so that on iterator EOF we can restore the current key and state. */ - size_t orig_key_len = it->key_len; - size_t orig_stack_items = it->stack.items; - raxNode *orig_node = it->node; - - while(1) { - int old_noup = noup; - - /* Already on head? Can't go up, iteration finished. */ - if (!noup && it->node == it->rt->head) { - it->flags |= RAX_ITER_EOF; - it->stack.items = orig_stack_items; - it->key_len = orig_key_len; - it->node = orig_node; - return 1; - } - - unsigned char prevchild = it->key[it->key_len-1]; - if (!noup) { - it->node = raxStackPop(&it->stack); - } else { - noup = 0; - } - - /* Adjust the current key to represent the node we are - * at. */ - int todel = it->node->iscompr ? it->node->size : 1; - raxIteratorDelChars(it,todel); - - /* Try visiting the prev child if there is at least one - * child. */ - if (!it->node->iscompr && it->node->size > (old_noup ? 0 : 1)) { - raxNode **cp = raxNodeLastChildPtr(it->node); - int i = it->node->size-1; - while (i >= 0) { - debugf("SCAN PREV %c\n", it->node->data[i]); - if (it->node->data[i] < prevchild) break; - i--; - cp--; - } - /* If we found a new subtree to explore in this node, - * go deeper following all the last children in order to - * find the key lexicographically greater. */ - if (i != -1) { - debugf("SCAN found a new node\n"); - /* Enter the node we just found. */ - if (!raxIteratorAddChars(it,it->node->data+i,1)) return 0; - if (!raxStackPush(&it->stack,it->node)) return 0; - memcpy(&it->node,cp,sizeof(it->node)); - /* Seek sub-tree max. */ - if (!raxSeekGreatest(it)) return 0; - } - } - - /* Return the key: this could be the key we found scanning a new - * subtree, or if we did not find a new subtree to explore here, - * before giving up with this node, check if it's a key itself. */ - if (it->node->iskey) { - it->data = raxGetData(it->node); - return 1; - } - } -} - -/* Seek an iterator at the specified element. - * Return 0 if the seek failed for syntax error or out of memory. Otherwise - * 1 is returned. When 0 is returned for out of memory, errno is set to - * the ENOMEM value. */ -int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) { - int eq = 0, lt = 0, gt = 0, first = 0, last = 0; - - it->stack.items = 0; /* Just resetting. Initialized by raxStart(). */ - it->flags |= RAX_ITER_JUST_SEEKED; - it->flags &= ~RAX_ITER_EOF; - it->key_len = 0; - it->node = NULL; - - /* Set flags according to the operator used to perform the seek. */ - if (op[0] == '>') { - gt = 1; - if (op[1] == '=') eq = 1; - } else if (op[0] == '<') { - lt = 1; - if (op[1] == '=') eq = 1; - } else if (op[0] == '=') { - eq = 1; - } else if (op[0] == '^') { - first = 1; - } else if (op[0] == '$') { - last = 1; - } else { - errno = 0; - return 0; /* Error. */ - } - - /* If there are no elements, set the EOF condition immediately and - * return. */ - if (it->rt->numele == 0) { - it->flags |= RAX_ITER_EOF; - return 1; - } - - if (first) { - /* Seeking the first key greater or equal to the empty string - * is equivalent to seeking the smaller key available. */ - return raxSeek(it,">=",NULL,0); - } - - if (last) { - /* Find the greatest key taking always the last child till a - * final node is found. */ - it->node = it->rt->head; - if (!raxSeekGreatest(it)) return 0; - assert(it->node->iskey); - it->data = raxGetData(it->node); - return 1; - } - - /* We need to seek the specified key. What we do here is to actually - * perform a lookup, and later invoke the prev/next key code that - * we already use for iteration. */ - int splitpos = 0; - size_t i = raxLowWalk(it->rt,ele,len,&it->node,NULL,&splitpos,&it->stack); - - /* Return OOM on incomplete stack info. */ - if (it->stack.oom) return 0; - - if (eq && i == len && (!it->node->iscompr || splitpos == 0) && - it->node->iskey) - { - /* We found our node, since the key matches and we have an - * "equal" condition. */ - if (!raxIteratorAddChars(it,ele,len)) return 0; /* OOM. */ - it->data = raxGetData(it->node); - } else if (lt || gt) { - /* Exact key not found or eq flag not set. We have to set as current - * key the one represented by the node we stopped at, and perform - * a next/prev operation to seek. */ - raxIteratorAddChars(it, ele, i-splitpos); - - /* We need to set the iterator in the correct state to call next/prev - * step in order to seek the desired element. */ - debugf("After initial seek: i=%d len=%d key=%.*s\n", - (int)i, (int)len, (int)it->key_len, it->key); - if (i != len && !it->node->iscompr) { - /* If we stopped in the middle of a normal node because of a - * mismatch, add the mismatching character to the current key - * and call the iterator with the 'noup' flag so that it will try - * to seek the next/prev child in the current node directly based - * on the mismatching character. */ - if (!raxIteratorAddChars(it,ele+i,1)) return 0; - debugf("Seek normal node on mismatch: %.*s\n", - (int)it->key_len, (char*)it->key); - - it->flags &= ~RAX_ITER_JUST_SEEKED; - if (lt && !raxIteratorPrevStep(it,1)) return 0; - if (gt && !raxIteratorNextStep(it,1)) return 0; - it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */ - } else if (i != len && it->node->iscompr) { - debugf("Compressed mismatch: %.*s\n", - (int)it->key_len, (char*)it->key); - /* In case of a mismatch within a compressed node. */ - int nodechar = it->node->data[splitpos]; - int keychar = ele[i]; - it->flags &= ~RAX_ITER_JUST_SEEKED; - if (gt) { - /* If the key the compressed node represents is greater - * than our seek element, continue forward, otherwise set the - * state in order to go back to the next sub-tree. */ - if (nodechar > keychar) { - if (!raxIteratorNextStep(it,0)) return 0; - } else { - if (!raxIteratorAddChars(it,it->node->data,it->node->size)) - return 0; - if (!raxIteratorNextStep(it,1)) return 0; - } - } - if (lt) { - /* If the key the compressed node represents is smaller - * than our seek element, seek the greater key in this - * subtree, otherwise set the state in order to go back to - * the previous sub-tree. */ - if (nodechar < keychar) { - if (!raxSeekGreatest(it)) return 0; - it->data = raxGetData(it->node); - } else { - if (!raxIteratorAddChars(it,it->node->data,it->node->size)) - return 0; - if (!raxIteratorPrevStep(it,1)) return 0; - } - } - it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */ - } else { - debugf("No mismatch: %.*s\n", - (int)it->key_len, (char*)it->key); - /* If there was no mismatch we are into a node representing the - * key, (but which is not a key or the seek operator does not - * include 'eq'), or we stopped in the middle of a compressed node - * after processing all the key. Continue iterating as this was - * a legitimate key we stopped at. */ - it->flags &= ~RAX_ITER_JUST_SEEKED; - if (it->node->iscompr && it->node->iskey && splitpos && lt) { - /* If we stopped in the middle of a compressed node with - * perfect match, and the condition is to seek a key "<" than - * the specified one, then if this node is a key it already - * represents our match. For instance we may have nodes: - * - * "f" -> "oobar" = 1 -> "" = 2 - * - * Representing keys "f" = 1, "foobar" = 2. A seek for - * the key < "foo" will stop in the middle of the "oobar" - * node, but will be our match, representing the key "f". - * - * So in that case, we don't seek backward. */ - it->data = raxGetData(it->node); - } else { - if (gt && !raxIteratorNextStep(it,0)) return 0; - if (lt && !raxIteratorPrevStep(it,0)) return 0; - } - it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */ - } - } else { - /* If we are here just eq was set but no match was found. */ - it->flags |= RAX_ITER_EOF; - return 1; - } - return 1; -} - -/* Go to the next element in the scope of the iterator 'it'. - * If EOF (or out of memory) is reached, 0 is returned, otherwise 1 is - * returned. In case 0 is returned because of OOM, errno is set to ENOMEM. */ -int raxNext(raxIterator *it) { - if (!raxIteratorNextStep(it,0)) { - errno = ENOMEM; - return 0; - } - if (it->flags & RAX_ITER_EOF) { - errno = 0; - return 0; - } - return 1; -} - -/* Go to the previous element in the scope of the iterator 'it'. - * If EOF (or out of memory) is reached, 0 is returned, otherwise 1 is - * returned. In case 0 is returned because of OOM, errno is set to ENOMEM. */ -int raxPrev(raxIterator *it) { - if (!raxIteratorPrevStep(it,0)) { - errno = ENOMEM; - return 0; - } - if (it->flags & RAX_ITER_EOF) { - errno = 0; - return 0; - } - return 1; -} - -/* Perform a random walk starting in the current position of the iterator. - * Return 0 if the tree is empty or on out of memory. Otherwise 1 is returned - * and the iterator is set to the node reached after doing a random walk - * of 'steps' steps. If the 'steps' argument is 0, the random walk is performed - * using a random number of steps between 1 and two times the logarithm of - * the number of elements. - * - * NOTE: if you use this function to generate random elements from the radix - * tree, expect a disappointing distribution. A random walk produces good - * random elements if the tree is not sparse, however in the case of a radix - * tree certain keys will be reported much more often than others. At least - * this function should be able to explore every possible element eventually. */ -int raxRandomWalk(raxIterator *it, size_t steps) { - if (it->rt->numele == 0) { - it->flags |= RAX_ITER_EOF; - return 0; - } - - if (steps == 0) { - size_t fle = 1+floor(log(it->rt->numele)); - fle *= 2; - steps = 1 + rand() % fle; - } - - raxNode *n = it->node; - while(steps > 0 || !n->iskey) { - int numchildren = n->iscompr ? 1 : n->size; - int r = rand() % (numchildren+(n != it->rt->head)); - - if (r == numchildren) { - /* Go up to parent. */ - n = raxStackPop(&it->stack); - int todel = n->iscompr ? n->size : 1; - raxIteratorDelChars(it,todel); - } else { - /* Select a random child. */ - if (n->iscompr) { - if (!raxIteratorAddChars(it,n->data,n->size)) return 0; - } else { - if (!raxIteratorAddChars(it,n->data+r,1)) return 0; - } - raxNode **cp = raxNodeFirstChildPtr(n)+r; - if (!raxStackPush(&it->stack,n)) return 0; - memcpy(&n,cp,sizeof(n)); - } - if (n->iskey) steps--; - } - it->node = n; - it->data = raxGetData(it->node); - return 1; -} - -/* Compare the key currently pointed by the iterator to the specified - * key according to the specified operator. Returns 1 if the comparison is - * true, otherwise 0 is returned. */ -int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len) { - int eq = 0, lt = 0, gt = 0; - - if (op[0] == '=' || op[1] == '=') eq = 1; - if (op[0] == '>') gt = 1; - else if (op[0] == '<') lt = 1; - else if (op[1] != '=') return 0; /* Syntax error. */ - - size_t minlen = key_len < iter->key_len ? key_len : iter->key_len; - int cmp = memcmp(iter->key,key,minlen); - - /* Handle == */ - if (lt == 0 && gt == 0) return cmp == 0 && key_len == iter->key_len; - - /* Handle >, >=, <, <= */ - if (cmp == 0) { - /* Same prefix: longer wins. */ - if (eq && key_len == iter->key_len) return 1; - else if (lt) return iter->key_len < key_len; - else if (gt) return iter->key_len > key_len; - else return 0; /* Avoid warning, just 'eq' is handled before. */ - } else if (cmp > 0) { - return gt ? 1 : 0; - } else /* (cmp < 0) */ { - return lt ? 1 : 0; - } -} - -/* Free the iterator. */ -void raxStop(raxIterator *it) { - if (it->key != it->key_static_string) rax_free(it->key); - raxStackFree(&it->stack); -} - -/* Return if the iterator is in an EOF state. This happens when raxSeek() - * failed to seek an appropriate element, so that raxNext() or raxPrev() - * will return zero, or when an EOF condition was reached while iterating - * with raxNext() and raxPrev(). */ -int raxEOF(raxIterator *it) { - return it->flags & RAX_ITER_EOF; -} - -/* Return the number of elements inside the radix tree. */ -uint64_t raxSize(rax *rax) { - return rax->numele; -} - -/* ----------------------------- Introspection ------------------------------ */ - -/* This function is mostly used for debugging and learning purposes. - * It shows an ASCII representation of a tree on standard output, outline - * all the nodes and the contained keys. - * - * The representation is as follow: - * - * "foobar" (compressed node) - * [abc] (normal node with three children) - * [abc]=0x12345678 (node is a key, pointing to value 0x12345678) - * [] (a normal empty node) - * - * Children are represented in new indented lines, each children prefixed by - * the "`-(x)" string, where "x" is the edge byte. - * - * [abc] - * `-(a) "ladin" - * `-(b) [kj] - * `-(c) [] - * - * However when a node has a single child the following representation - * is used instead: - * - * [abc] -> "ladin" -> [] - */ - -/* The actual implementation of raxShow(). */ -void raxRecursiveShow(int level, int lpad, raxNode *n) { - char s = n->iscompr ? '"' : '['; - char e = n->iscompr ? '"' : ']'; - - int numchars = printf("%c%.*s%c", s, n->size, n->data, e); - if (n->iskey) { - numchars += printf("=%p",raxGetData(n)); - } - - int numchildren = n->iscompr ? 1 : n->size; - /* Note that 7 and 4 magic constants are the string length - * of " `-(x) " and " -> " respectively. */ - if (level) { - lpad += (numchildren > 1) ? 7 : 4; - if (numchildren == 1) lpad += numchars; - } - raxNode **cp = raxNodeFirstChildPtr(n); - for (int i = 0; i < numchildren; i++) { - char *branch = " `-(%c) "; - if (numchildren > 1) { - printf("\n"); - for (int j = 0; j < lpad; j++) putchar(' '); - printf(branch,n->data[i]); - } else { - printf(" -> "); - } - raxNode *child; - memcpy(&child,cp,sizeof(child)); - raxRecursiveShow(level+1,lpad,child); - cp++; - } -} - -/* Show a tree, as outlined in the comment above. */ -void raxShow(rax *rax) { - raxRecursiveShow(0,0,rax->head); - putchar('\n'); -} - -/* Used by debugnode() macro to show info about a given node. */ -void raxDebugShowNode(const char *msg, raxNode *n) { - if (raxDebugMsg == 0) return; - printf("%s: %p [%.*s] key:%u size:%u children:", - msg, (void*)n, (int)n->size, (char*)n->data, n->iskey, n->size); - int numcld = n->iscompr ? 1 : n->size; - raxNode **cldptr = raxNodeLastChildPtr(n) - (numcld-1); - while(numcld--) { - raxNode *child; - memcpy(&child,cldptr,sizeof(child)); - cldptr++; - printf("%p ", (void*)child); - } - printf("\n"); - fflush(stdout); -} - -/* Touch all the nodes of a tree returning a check sum. This is useful - * in order to make Valgrind detect if there is something wrong while - * reading the data structure. - * - * This function was used in order to identify Rax bugs after a big refactoring - * using this technique: - * - * 1. The rax-test is executed using Valgrind, adding a printf() so that for - * the fuzz tester we see what iteration in the loop we are in. - * 2. After every modification of the radix tree made by the fuzz tester - * in rax-test.c, we add a call to raxTouch(). - * 3. Now as soon as an operation will corrupt the tree, raxTouch() will - * detect it (via Valgrind) immediately. We can add more calls to narrow - * the state. - * 4. At this point a good idea is to enable Rax debugging messages immediately - * before the moment the tree is corrupted, to see what happens. - */ -unsigned long raxTouch(raxNode *n) { - debugf("Touching %p\n", (void*)n); - unsigned long sum = 0; - if (n->iskey) { - sum += (unsigned long)raxGetData(n); - } - - int numchildren = n->iscompr ? 1 : n->size; - raxNode **cp = raxNodeFirstChildPtr(n); - int count = 0; - for (int i = 0; i < numchildren; i++) { - if (numchildren > 1) { - sum += (long)n->data[i]; - } - raxNode *child; - memcpy(&child,cp,sizeof(child)); - if (child == (void*)0x65d1760) count++; - if (count > 1) exit(1); - sum += raxTouch(child); - cp++; - } - return sum; -} diff --git a/src/libsysprof/rax.h b/src/libsysprof/rax.h deleted file mode 100644 index 6b1fd418..00000000 --- a/src/libsysprof/rax.h +++ /dev/null @@ -1,216 +0,0 @@ -/* Rax -- A radix tree implementation. - * - * Copyright (c) 2017-2018, Salvatore Sanfilippo - * 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 Redis 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 RAX_H -#define RAX_H - -#include - -/* Representation of a radix tree as implemented in this file, that contains - * the strings "foo", "foobar" and "footer" after the insertion of each - * word. When the node represents a key inside the radix tree, we write it - * between [], otherwise it is written between (). - * - * This is the vanilla representation: - * - * (f) "" - * \ - * (o) "f" - * \ - * (o) "fo" - * \ - * [t b] "foo" - * / \ - * "foot" (e) (a) "foob" - * / \ - * "foote" (r) (r) "fooba" - * / \ - * "footer" [] [] "foobar" - * - * However, this implementation implements a very common optimization where - * successive nodes having a single child are "compressed" into the node - * itself as a string of characters, each representing a next-level child, - * and only the link to the node representing the last character node is - * provided inside the representation. So the above representation is turned - * into: - * - * ["foo"] "" - * | - * [t b] "foo" - * / \ - * "foot" ("er") ("ar") "foob" - * / \ - * "footer" [] [] "foobar" - * - * However this optimization makes the implementation a bit more complex. - * For instance if a key "first" is added in the above radix tree, a - * "node splitting" operation is needed, since the "foo" prefix is no longer - * composed of nodes having a single child one after the other. This is the - * above tree and the resulting node splitting after this event happens: - * - * - * (f) "" - * / - * (i o) "f" - * / \ - * "firs" ("rst") (o) "fo" - * / \ - * "first" [] [t b] "foo" - * / \ - * "foot" ("er") ("ar") "foob" - * / \ - * "footer" [] [] "foobar" - * - * Similarly after deletion, if a new chain of nodes having a single child - * is created (the chain must also not include nodes that represent keys), - * it must be compressed back into a single node. - * - */ - -#define RAX_NODE_MAX_SIZE ((1<<29)-1) -typedef struct raxNode { - uint32_t iskey:1; /* Does this node contain a key? */ - uint32_t isnull:1; /* Associated value is NULL (don't store it). */ - uint32_t iscompr:1; /* Node is compressed. */ - uint32_t size:29; /* Number of children, or compressed string len. */ - /* Data layout is as follows: - * - * If node is not compressed we have 'size' bytes, one for each children - * character, and 'size' raxNode pointers, point to each child node. - * Note how the character is not stored in the children but in the - * edge of the parents: - * - * [header iscompr=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?) - * - * if node is compressed (iscompr bit is 1) the node has 1 children. - * In that case the 'size' bytes of the string stored immediately at - * the start of the data section, represent a sequence of successive - * nodes linked one after the other, for which only the last one in - * the sequence is actually represented as a node, and pointed to by - * the current compressed node. - * - * [header iscompr=1][xyz][z-ptr](value-ptr?) - * - * Both compressed and not compressed nodes can represent a key - * with associated data in the radix tree at any level (not just terminal - * nodes). - * - * If the node has an associated key (iskey=1) and is not NULL - * (isnull=0), then after the raxNode pointers pointing to the - * children, an additional value pointer is present (as you can see - * in the representation above as "value-ptr" field). - */ - unsigned char data[]; -} raxNode; - -typedef struct rax { - raxNode *head; - uint64_t numele; - uint64_t numnodes; -} rax; - -/* Stack data structure used by raxLowWalk() in order to, optionally, return - * a list of parent nodes to the caller. The nodes do not have a "parent" - * field for space concerns, so we use the auxiliary stack when needed. */ -#define RAX_STACK_STATIC_ITEMS 32 -typedef struct raxStack { - void **stack; /* Points to static_items or an heap allocated array. */ - size_t items, maxitems; /* Number of items contained and total space. */ - /* Up to RAXSTACK_STACK_ITEMS items we avoid to allocate on the heap - * and use this static array of pointers instead. */ - void *static_items[RAX_STACK_STATIC_ITEMS]; - int oom; /* True if pushing into this stack failed for OOM at some point. */ -} raxStack; - -/* Optional callback used for iterators and be notified on each rax node, - * including nodes not representing keys. If the callback returns true - * the callback changed the node pointer in the iterator structure, and the - * iterator implementation will have to replace the pointer in the radix tree - * internals. This allows the callback to reallocate the node to perform - * very special operations, normally not needed by normal applications. - * - * This callback is used to perform very low level analysis of the radix tree - * structure, scanning each possible node (but the root node), or in order to - * reallocate the nodes to reduce the allocation fragmentation (this is the - * Redis application for this callback). - * - * This is currently only supported in forward iterations (raxNext) */ -typedef int (*raxNodeCallback)(raxNode **noderef); - -/* Radix tree iterator state is encapsulated into this data structure. */ -#define RAX_ITER_STATIC_LEN 128 -#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current - element for the first iteration and - clear the flag. */ -#define RAX_ITER_EOF (1<<1) /* End of iteration reached. */ -#define RAX_ITER_SAFE (1<<2) /* Safe iterator, allows operations while - iterating. But it is slower. */ -typedef struct raxIterator { - int flags; - rax *rt; /* Radix tree we are iterating. */ - unsigned char *key; /* The current string. */ - void *data; /* Data associated to this key. */ - size_t key_len; /* Current key length. */ - size_t key_max; /* Max key len the current key buffer can hold. */ - unsigned char key_static_string[RAX_ITER_STATIC_LEN]; - raxNode *node; /* Current node. Only for unsafe iteration. */ - raxStack stack; /* Stack used for unsafe iteration. */ - raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */ -} raxIterator; - -/* A special pointer returned for not found items. */ -extern void *raxNotFound; - -/* Exported API. */ -rax *raxNew(void); -int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); -int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); -int raxRemove(rax *rax, unsigned char *s, size_t len, void **old); -void *raxFind(rax *rax, unsigned char *s, size_t len); -void raxFree(rax *rax); -void raxFreeWithCallback(rax *rax, void (*free_callback)(void*)); -void raxStart(raxIterator *it, rax *rt); -int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len); -int raxNext(raxIterator *it); -int raxPrev(raxIterator *it); -int raxRandomWalk(raxIterator *it, size_t steps); -int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len); -void raxStop(raxIterator *it); -int raxEOF(raxIterator *it); -void raxShow(rax *rax); -uint64_t raxSize(rax *rax); -unsigned long raxTouch(raxNode *n); -void raxSetDebugMsg(int onoff); - -/* Internal API. May be used by the node callback in order to access rax nodes - * in a low level way, so this function is exported as well. */ -void raxSetData(raxNode *n, void *data); - -#endif diff --git a/src/libsysprof/rax_malloc.h b/src/libsysprof/rax_malloc.h deleted file mode 100644 index e9d5d5d7..00000000 --- a/src/libsysprof/rax_malloc.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Rax -- A radix tree implementation. - * - * Copyright (c) 2017, Salvatore Sanfilippo - * 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 Redis 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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. - */ - -/* Allocator selection. - * - * This file is used in order to change the Rax allocator at compile time. - * Just define the following defines to what you want to use. Also add - * the include of your alternate allocator if needed (not needed in order - * to use the default libc allocator). */ - -#ifndef RAX_ALLOC_H -#define RAX_ALLOC_H -#define rax_malloc malloc -#define rax_realloc realloc -#define rax_free free -#endif diff --git a/src/libsysprof-ui/sysprof-environ-editor.h b/src/libsysprof/sysprof-address-layout-private.h similarity index 52% rename from src/libsysprof-ui/sysprof-environ-editor.h rename to src/libsysprof/sysprof-address-layout-private.h index 403ab797..15bce8d4 100644 --- a/src/libsysprof-ui/sysprof-environ-editor.h +++ b/src/libsysprof/sysprof-address-layout-private.h @@ -1,6 +1,6 @@ -/* sysprof-environ-editor.h +/* sysprof-address-layout.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,19 +20,20 @@ #pragma once -#include +#include -#include "sysprof-environ.h" +#include "sysprof-document-mmap.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_ENVIRON_EDITOR (sysprof_environ_editor_get_type()) +#define SYSPROF_TYPE_ADDRESS_LAYOUT (sysprof_address_layout_get_type()) -G_DECLARE_FINAL_TYPE (SysprofEnvironEditor, sysprof_environ_editor, SYSPROF, ENVIRON_EDITOR, GtkWidget) +G_DECLARE_FINAL_TYPE (SysprofAddressLayout, sysprof_address_layout, SYSPROF, ADDRESS_LAYOUT, GObject) -GtkWidget *sysprof_environ_editor_new (void); -SysprofEnviron *sysprof_environ_editor_get_environ (SysprofEnvironEditor *self); -void sysprof_environ_editor_set_environ (SysprofEnvironEditor *self, - SysprofEnviron *environ); +SysprofAddressLayout *sysprof_address_layout_new (void); +void sysprof_address_layout_take (SysprofAddressLayout *self, + SysprofDocumentMmap *map); +SysprofDocumentMmap *sysprof_address_layout_lookup (SysprofAddressLayout *self, + SysprofAddress address); G_END_DECLS diff --git a/src/libsysprof/sysprof-address-layout.c b/src/libsysprof/sysprof-address-layout.c new file mode 100644 index 00000000..ff92fe26 --- /dev/null +++ b/src/libsysprof/sysprof-address-layout.c @@ -0,0 +1,227 @@ +/* sysprof-address-layout.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include "sysprof-address-layout-private.h" + +struct _SysprofAddressLayout +{ + GObject parent_instance; + GPtrArray *mmaps; + guint mmaps_dirty : 1; +}; + +static guint +sysprof_address_layout_get_n_items (GListModel *model) +{ + return SYSPROF_ADDRESS_LAYOUT (model)->mmaps->len; +} + +static GType +sysprof_address_layout_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_DOCUMENT_MMAP; +} + +static gpointer +sysprof_address_layout_get_item (GListModel *model, + guint position) +{ + SysprofAddressLayout *self = SYSPROF_ADDRESS_LAYOUT (model); + + if (position >= self->mmaps->len) + return NULL; + + return g_object_ref (g_ptr_array_index (self->mmaps, position)); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_n_items = sysprof_address_layout_get_n_items; + iface->get_item = sysprof_address_layout_get_item; + iface->get_item_type = sysprof_address_layout_get_item_type; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofAddressLayout, sysprof_address_layout, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static void +sysprof_address_layout_finalize (GObject *object) +{ + SysprofAddressLayout *self = (SysprofAddressLayout *)object; + + g_clear_pointer (&self->mmaps, g_ptr_array_unref); + + G_OBJECT_CLASS (sysprof_address_layout_parent_class)->finalize (object); +} + +static void +sysprof_address_layout_class_init (SysprofAddressLayoutClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_address_layout_finalize; +} + +static void +sysprof_address_layout_init (SysprofAddressLayout *self) +{ + self->mmaps = g_ptr_array_new_with_free_func (g_object_unref); +} + +SysprofAddressLayout * +sysprof_address_layout_new (void) +{ + return g_object_new (SYSPROF_TYPE_ADDRESS_LAYOUT, NULL); +} + +void +sysprof_address_layout_take (SysprofAddressLayout *self, + SysprofDocumentMmap *map) +{ + g_return_if_fail (SYSPROF_IS_ADDRESS_LAYOUT (self)); + g_return_if_fail (SYSPROF_IS_DOCUMENT_MMAP (map)); + + g_ptr_array_add (self->mmaps, map); + + self->mmaps_dirty = TRUE; +} + +static int +compare_mmaps (gconstpointer a, + gconstpointer b) +{ + SysprofDocumentMmap *mmap_a = *(SysprofDocumentMmap * const *)a; + SysprofDocumentMmap *mmap_b = *(SysprofDocumentMmap * const *)b; + guint64 begin_a = sysprof_document_mmap_get_start_address (mmap_a); + guint64 begin_b = sysprof_document_mmap_get_start_address (mmap_b); + guint64 end_a = sysprof_document_mmap_get_end_address (mmap_a); + guint64 end_b = sysprof_document_mmap_get_end_address (mmap_b); + + if (begin_a < begin_b) + return -1; + + if (begin_a > begin_b) + return 1; + + if (end_a < end_b) + return -1; + + if (end_a > end_b) + return 1; + + return 0; +} + +static gboolean +mmaps_overlap (SysprofDocumentMmap *a, + SysprofDocumentMmap *b) +{ + if ((sysprof_document_mmap_get_start_address (a) <= sysprof_document_mmap_get_start_address (b)) && + (sysprof_document_mmap_get_end_address (a) > sysprof_document_mmap_get_start_address (b))) + return TRUE; + + return FALSE; +} + +static EggBitset * +find_duplicates (GPtrArray *sorted) +{ + EggBitset *bitset = egg_bitset_new_empty (); + + if (sorted->len == 0) + return bitset; + + for (guint i = 0; i < sorted->len-1; i++) + { + SysprofDocumentMmap *map = g_ptr_array_index (sorted, i); + SysprofDocumentMmap *next = g_ptr_array_index (sorted, i+1); + + /* Take the second one if they overlap, which is generally the large of + * the mmaps. That can happen when something like [stack] is resized into + * a larger mmap. It's useful to remove duplicates so we get more + * predictable bsearch results. + */ + if (mmaps_overlap (map, next)) + egg_bitset_add (bitset, i); + } + + return bitset; +} + +static int +find_by_address (gconstpointer a, + gconstpointer b) +{ + const SysprofAddress *key = a; + SysprofDocumentMmap *map = *(SysprofDocumentMmap * const *)b; + + if (*key < sysprof_document_mmap_get_start_address (map)) + return -1; + + if (*key >= sysprof_document_mmap_get_end_address (map)) + return 1; + + return 0; +} + +SysprofDocumentMmap * +sysprof_address_layout_lookup (SysprofAddressLayout *self, + SysprofAddress address) +{ + SysprofDocumentMmap **ret; + + g_return_val_if_fail (SYSPROF_IS_ADDRESS_LAYOUT (self), NULL); + + if (self->mmaps_dirty) + { + g_autoptr(EggBitset) dups = NULL; + EggBitsetIter iter; + guint old_len = self->mmaps->len; + guint i; + + self->mmaps_dirty = FALSE; + + g_ptr_array_sort (self->mmaps, compare_mmaps); + dups = find_duplicates (self->mmaps); + + if (egg_bitset_iter_init_last (&iter, dups, &i)) + { + do + g_ptr_array_remove_index (self->mmaps, i); + while (egg_bitset_iter_previous (&iter, &i)); + } + + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_len, self->mmaps->len); + } + + ret = bsearch (&address, + self->mmaps->pdata, + self->mmaps->len, + sizeof (gpointer), + find_by_address); + + return ret ? *ret : NULL; +} diff --git a/src/libsysprof/sysprof-backport-autocleanups.h b/src/libsysprof/sysprof-backport-autocleanups.h deleted file mode 100644 index 381b2735..00000000 --- a/src/libsysprof/sysprof-backport-autocleanups.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "config.h" - -#include - -#if HAVE_POLKIT -# ifndef HAVE_POLKIT_AUTOPTR -# include - - G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthority, g_object_unref) - G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitAuthorizationResult, g_object_unref) - G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitSubject, g_object_unref) - G_DEFINE_AUTOPTR_CLEANUP_FUNC (PolkitDetails, g_object_unref) -# endif -#endif - -#if !GLIB_CHECK_VERSION(2, 56, 0) -# define g_clear_handle_id(ptr, clear_func) \ - G_STMT_START { \ - guint __ptr = *(ptr); \ - *(ptr) = 0; \ - if (__ptr != 0) \ - clear_func (__ptr); \ - } G_STMT_END -#endif diff --git a/src/libsysprof/sysprof-battery-charge.c b/src/libsysprof/sysprof-battery-charge.c new file mode 100644 index 00000000..5fbb3bd4 --- /dev/null +++ b/src/libsysprof/sysprof-battery-charge.c @@ -0,0 +1,306 @@ +/* sysprof-battery-charge.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "line-reader-private.h" + +#include "sysprof-battery-charge.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +#define SYS_CLASS_POWER_SUPPLY "/sys/class/power_supply/" + +struct _SysprofBatteryCharge +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofBatteryChargeClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofBatteryCharge, sysprof_battery_charge, SYSPROF_TYPE_INSTRUMENT) + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; +} Record; + +static void +record_free (gpointer data) +{ + Record *record = data; + + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_free (record); +} + +static void +clear_fd (gpointer ptr) +{ + int *fdptr = ptr; + g_clear_fd (fdptr, NULL); +} + +static char ** +collect_battery_names (void) +{ + GPtrArray *ar = g_ptr_array_new (); + g_autoptr(GDir) dir = NULL; + + if ((dir = g_dir_open (SYS_CLASS_POWER_SUPPLY, 0, NULL))) + { + const char *name; + + while ((name = g_dir_read_name (dir))) + { + if (strcmp (name, "AC") == 0) + continue; + + g_ptr_array_add (ar, g_strdup (name)); + } + } + + g_ptr_array_add (ar, NULL); + + return (char **)g_ptr_array_free (ar, FALSE); +} + +typedef struct _ReadBuffer +{ + char buf[32]; +} ReadBuffer; + +static DexFuture * +sysprof_battery_charge_record_fiber (gpointer user_data) +{ + const int invalid_fd = -1; + g_autofree guint *ids = NULL; + g_autofree SysprofCaptureCounterValue *values = NULL; + g_autofree SysprofCaptureCounter *counters = NULL; + g_autofree ReadBuffer *bufs = NULL; + SysprofCaptureWriter *writer; + Record *record = user_data; + g_autoptr(GArray) charge_fds = NULL; + g_auto(GStrv) names = NULL; + guint n_names; + guint n_counters = 1; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + writer = _sysprof_recording_writer (record->recording); + names = collect_battery_names (); + n_names = g_strv_length (names); + + /* Use some stack space for our counters and values. */ + ids = g_new0 (guint, n_names + 1); + counters = g_new0 (SysprofCaptureCounter, n_names + 1); + values = g_new0 (SysprofCaptureCounterValue, n_names + 1); + bufs = g_new0 (ReadBuffer, n_names + 1); + + /* Setup the combined counter which is the total charge of all of + * the batteries we discover on the system. + */ + counters[0].id = ids[0] = sysprof_capture_writer_request_counter (writer, 1); + g_strlcpy (counters[0].category, "Battery Charge", sizeof counters[0].category); + g_strlcpy (counters[0].name, "Combined", sizeof counters[0].name); + g_snprintf (counters[0].description, sizeof counters[0].description, "Combined Battery Charge (µAh)"); + counters[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + counters[0].value.v64 = 0; + + /* Create array to store open FDs to the charge file. We'll do a + * positioned read at 0 on these so we get new data on each subsequent + * read via AIO. Set the first FD to invalid since that will map to the + * position of the combined-counter. + */ + charge_fds = g_array_new (FALSE, FALSE, sizeof (int)); + g_array_set_clear_func (charge_fds, clear_fd); + g_array_append_val (charge_fds, invalid_fd); + + for (guint i = 0; names[i]; i++) + { + SysprofCaptureCounter *counter = &counters[n_counters]; + const char *name = names[i]; + g_autofree char *charge_path = g_build_filename (SYS_CLASS_POWER_SUPPLY, name, "charge_now", NULL); + g_autofree char *model_name_path = g_build_filename (SYS_CLASS_POWER_SUPPLY, name, "model_name", NULL); + g_autofree char *type_path = g_build_filename (SYS_CLASS_POWER_SUPPLY, name, "type", NULL); + g_autofree char *model_name_data = NULL; + g_autofree char *type_data = NULL; + g_autofd int charge_fd = -1; + + if (!g_file_get_contents (type_path, &type_data, NULL, NULL) || + !g_str_has_prefix (type_data, "Battery") || + -1 == (charge_fd = open (charge_path, O_RDONLY|O_CLOEXEC))) + continue; + + counter->id = ids[n_counters] = sysprof_capture_writer_request_counter (writer, 1); + counter->type = SYSPROF_CAPTURE_COUNTER_INT64; + g_strlcpy (counter->category, "Battery Charge", sizeof counter->description); + if (g_file_get_contents (model_name_path, &model_name_data, NULL, NULL)) + g_strlcpy (counter->name, g_strstrip (model_name_data), sizeof counter->name); + else + g_strlcpy (counter->name, name, sizeof counter->name); + g_snprintf (counter->description, sizeof counter->description, "%s (µAh)", counter->name); + counter->value.v64 = 0; + + g_array_append_val (charge_fds, charge_fd); + charge_fd = -1; + + n_counters++; + } + + /* If we only have the combined counter, then just short-circuit and + * don't record any counters. Otherwise the battery charge row might + * show up in UI which we would want to omit. + */ + if (n_counters == 1) + return dex_future_new_for_boolean (TRUE); + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + counters, + n_counters); + + /* Main recording loop */ + for (;;) + { + g_autoptr(GPtrArray) futures = g_ptr_array_new_with_free_func (dex_unref); + + /* Read the contents of all the charge buffers in a single + * submission to the AIO layer. + */ + g_ptr_array_add (futures, dex_future_new_for_boolean (TRUE)); + for (guint i = 1; i < charge_fds->len; i++) + { + int charge_fd = g_array_index (charge_fds, int, i); + + g_ptr_array_add (futures, + dex_aio_read (NULL, + charge_fd, + &bufs[i].buf, + sizeof bufs[i].buf-1, + 0)); + } + + /* Now wait until all the reads complete */ + if (futures->len > 0) + dex_await (dex_future_anyv ((DexFuture **)futures->pdata, futures->len), NULL); + + /* Parse the current charge values */ + values[0].v64 = 0; + for (guint i = 1; i < charge_fds->len; i++) + { + gssize len = dex_await_int64 (dex_ref (g_ptr_array_index (futures, i)), NULL); + guint64 v64; + + if (len <= 0) + continue; + + errno = 0; + bufs[i].buf[len] = 0; + v64 = g_ascii_strtoull (bufs[i].buf, NULL, 10); + if (v64 == G_MAXUINT64 || errno != 0) + continue; + + values[i].v64 = v64; + values[0].v64 += v64; + } + + /* Deliver new values to capture */ + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ids, + values, + n_counters); + + /* Wait for cancellation or ½ second */ + dex_await (dex_future_first (dex_ref (record->cancellable), + dex_timeout_new_usec (G_USEC_PER_SEC / 2), + NULL), + NULL); + + /* If cancellable is rejected, then we're done recording */ + if (dex_future_get_status (record->cancellable) != DEX_FUTURE_STATUS_PENDING) + break; + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_battery_charge_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_BATTERY_CHARGE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_battery_charge_record_fiber, + record, + record_free); +} + +static void +sysprof_battery_charge_class_init (SysprofBatteryChargeClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_battery_charge_record; +} + +static void +sysprof_battery_charge_init (SysprofBatteryCharge *self) +{ +} + +SysprofInstrument * +sysprof_battery_charge_new (void) +{ + return g_object_new (SYSPROF_TYPE_BATTERY_CHARGE, NULL); +} diff --git a/src/libsysprof/sysprof-battery-charge.h b/src/libsysprof/sysprof-battery-charge.h new file mode 100644 index 00000000..1d3093d1 --- /dev/null +++ b/src/libsysprof/sysprof-battery-charge.h @@ -0,0 +1,42 @@ +/* sysprof-battery-charge.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_BATTERY_CHARGE (sysprof_battery_charge_get_type()) +#define SYSPROF_IS_BATTERY_CHARGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_BATTERY_CHARGE) +#define SYSPROF_BATTERY_CHARGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_BATTERY_CHARGE, SysprofBatteryCharge) +#define SYSPROF_BATTERY_CHARGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_BATTERY_CHARGE, SysprofBatteryChargeClass) + +typedef struct _SysprofBatteryCharge SysprofBatteryCharge; +typedef struct _SysprofBatteryChargeClass SysprofBatteryChargeClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_battery_charge_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_battery_charge_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofBatteryCharge, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-battery-source.c b/src/libsysprof/sysprof-battery-source.c deleted file mode 100644 index 7081357f..00000000 --- a/src/libsysprof/sysprof-battery-source.c +++ /dev/null @@ -1,343 +0,0 @@ -/* sysprof-battery-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-battery-source" - -#include "config.h" - -#include -#include -#include - -#include "sysprof-backport-autocleanups.h" -#include "sysprof-battery-source.h" -#include "sysprof-helpers.h" - -struct _SysprofBatterySource -{ - GObject parent_instance; - - SysprofCaptureWriter *writer; - GArray *batteries; - - guint combined_id; - guint poll_source; -}; - -typedef struct -{ - gchar id[32]; - gchar name[52]; - guint charge_full; - guint charge_now; - gint charge_now_fd; - guint counter_id; -} Battery; - -static void source_iface_init (SysprofSourceInterface *); - -G_DEFINE_TYPE_WITH_CODE (SysprofBatterySource, sysprof_battery_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -battery_clear (gpointer data) -{ - Battery *b = data; - - if (b->charge_now_fd != -1) - close (b->charge_now_fd); -} - -static gboolean -sysprof_battery_source_get_is_ready (SysprofSource *source) -{ - return TRUE; -} - -static void -sysprof_battery_source_prepare (SysprofSource *source) -{ - SysprofBatterySource *self = (SysprofBatterySource *)source; - g_autoptr(GDir) dir = NULL; - g_autoptr(GArray) counters = NULL; - const gchar *name; - - g_assert (SYSPROF_IS_BATTERY_SOURCE (self)); - -#define BAT_BASE_PATH "/sys/class/power_supply/" - - counters = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounter)); - - if (!(dir = g_dir_open (BAT_BASE_PATH, 0, NULL))) - goto emit_ready; - - while ((name = g_dir_read_name (dir))) - { - g_autofree gchar *type_path = g_strdup_printf (BAT_BASE_PATH "%s/type", name); - g_autofree gchar *model_path = g_strdup_printf (BAT_BASE_PATH "%s/model_name", name); - g_autofree gchar *charge_now_path = g_strdup_printf (BAT_BASE_PATH "%s/charge_now", name); - g_autofree gchar *charge_full_path = g_strdup_printf (BAT_BASE_PATH "%s/charge_full", name); - g_autofree gchar *type_data = NULL; - g_autofree gchar *model_data = NULL; - g_autofree gchar *charge_full_data = NULL; - SysprofCaptureCounter ctr; - Battery bat = {{0}}; - - /* We dn't care about AC */ - if (g_strcmp0 (name, "AC") == 0) - continue; - - if (!g_file_get_contents (type_path, &type_data, NULL, NULL) || - !g_str_has_prefix (type_data, "Battery")) - continue; - - g_strlcpy (bat.id, name, sizeof bat.id); - - if (g_file_get_contents (model_path, &model_data, NULL, NULL)) - g_strlcpy (bat.name, model_data, sizeof bat.name); - - if (g_file_get_contents (charge_full_path, &charge_full_data, NULL, NULL)) - bat.charge_full = atoi (charge_full_data); - - /* Wait for first polling */ - bat.charge_now = 0; - - g_strstrip (bat.id); - g_strstrip (bat.name); - - bat.charge_now_fd = open (charge_now_path, O_RDONLY); - - if (bat.charge_now_fd == -1) - continue; - - bat.counter_id = sysprof_capture_writer_request_counter (self->writer, 1); - - g_strlcpy (ctr.category, "Battery Charge", sizeof ctr.category); - g_strlcpy (ctr.name, bat.id, sizeof ctr.name); - g_snprintf (ctr.description, sizeof ctr.description, "%s (µAh)", bat.name); - ctr.id = bat.counter_id; - ctr.type = SYSPROF_CAPTURE_COUNTER_INT64; - - g_array_append_val (self->batteries, bat); - g_array_append_val (counters, ctr); - } - - if (counters->len > 0) - { - SysprofCaptureCounter ctr = {{0}}; - - self->combined_id = sysprof_capture_writer_request_counter (self->writer, 1); - - /* Add combined counter */ - g_strlcpy (ctr.category, "Battery Charge", sizeof ctr.category); - g_strlcpy (ctr.name, "Combined", sizeof ctr.name); - g_snprintf (ctr.description, sizeof ctr.description, "Combined Battery Charge (µAh)"); - ctr.id = self->combined_id; - ctr.type = SYSPROF_CAPTURE_COUNTER_INT64; - - g_array_append_val (counters, ctr); - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - (gpointer)counters->data, - counters->len); - } - -#undef BAT_BASE_PATH - -emit_ready: - sysprof_source_emit_ready (source); -} - -static gboolean -battery_poll (Battery *battery, - SysprofCaptureCounterValue *value) -{ - gint64 val; - gssize len; - gchar buf[32]; - - g_assert (battery != NULL); - - if (battery->charge_now_fd == -1) - return FALSE; - - if (lseek (battery->charge_now_fd, 0, SEEK_SET) != 0) - { - close (battery->charge_now_fd); - battery->charge_now_fd = -1; - return FALSE; - } - - len = read (battery->charge_now_fd, buf, sizeof buf - 1); - - if (len < 0) - { - close (battery->charge_now_fd); - battery->charge_now_fd = -1; - return FALSE; - } - - buf [len] = 0; - - val = atoi (buf); - - if (val != battery->charge_now) - { - battery->charge_now = val; - value->v64 = val; - return TRUE; - } - - return FALSE; -} - -static gboolean -sysprof_battery_source_poll_cb (gpointer data) -{ - SysprofBatterySource *self = data; - g_autoptr(GArray) values = NULL; - g_autoptr(GArray) ids = NULL; - gint64 combined = 0; - - g_assert (SYSPROF_IS_BATTERY_SOURCE (self)); - - values = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounterValue)); - ids = g_array_new (FALSE, FALSE, sizeof (guint)); - - for (guint i = 0; i < self->batteries->len; i++) - { - Battery *battery = &g_array_index (self->batteries, Battery, i); - SysprofCaptureCounterValue value; - - if G_LIKELY (battery_poll (battery, &value)) - { - combined += value.v64; - g_array_append_val (ids, battery->counter_id); - g_array_append_val (values, value); - } - } - - if (values->len > 0) - { - if (self->combined_id != 0) - { - SysprofCaptureCounterValue value = { .v64 = combined, }; - - g_array_append_val (ids, self->combined_id); - g_array_append_val (values, value); - } - - sysprof_capture_writer_set_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - (gconstpointer)ids->data, - (gconstpointer)values->data, - ids->len); - } - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_battery_source_start (SysprofSource *source) -{ - SysprofBatterySource *self = (SysprofBatterySource *)source; - - g_assert (SYSPROF_IS_BATTERY_SOURCE (self)); - - self->poll_source = g_timeout_add_seconds (1, sysprof_battery_source_poll_cb, self); - - /* Poll immediately */ - sysprof_battery_source_poll_cb (self); -} - -static void -sysprof_battery_source_stop (SysprofSource *source) -{ - SysprofBatterySource *self = (SysprofBatterySource *)source; - - g_assert (SYSPROF_IS_BATTERY_SOURCE (self)); - - /* Poll one last time */ - sysprof_battery_source_poll_cb (self); - - g_clear_handle_id (&self->poll_source, g_source_remove); - - sysprof_source_emit_finished (source); -} - -static void -sysprof_battery_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofBatterySource *self = (SysprofBatterySource *)source; - - g_assert (SYSPROF_IS_BATTERY_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->get_is_ready = sysprof_battery_source_get_is_ready; - iface->prepare = sysprof_battery_source_prepare; - iface->set_writer = sysprof_battery_source_set_writer; - iface->start = sysprof_battery_source_start; - iface->stop = sysprof_battery_source_stop; -} - -static void -sysprof_battery_source_finalize (GObject *object) -{ - SysprofBatterySource *self = (SysprofBatterySource *)object; - - g_clear_pointer (&self->batteries, g_array_unref); - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - - G_OBJECT_CLASS (sysprof_battery_source_parent_class)->finalize (object); -} - -static void -sysprof_battery_source_class_init (SysprofBatterySourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_battery_source_finalize; -} - -static void -sysprof_battery_source_init (SysprofBatterySource *self) -{ - self->batteries = g_array_new (FALSE, FALSE, sizeof (Battery)); - g_array_set_clear_func (self->batteries, battery_clear); -} - -SysprofSource * -sysprof_battery_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_BATTERY_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-bundled-symbolizer-private.h b/src/libsysprof/sysprof-bundled-symbolizer-private.h new file mode 100644 index 00000000..086d4eaa --- /dev/null +++ b/src/libsysprof/sysprof-bundled-symbolizer-private.h @@ -0,0 +1,39 @@ +/* sysprof-bundled-symbolizer-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-bundled-symbolizer.h" + +G_BEGIN_DECLS + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPackedSymbol +{ + SysprofCaptureAddress addr_begin; + SysprofCaptureAddress addr_end; + guint32 pid; + guint32 offset; + guint32 tag_offset; + guint32 padding; +} SysprofPackedSymbol +SYSPROF_ALIGNED_END(1); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-bundled-symbolizer.c b/src/libsysprof/sysprof-bundled-symbolizer.c new file mode 100644 index 00000000..421f9f43 --- /dev/null +++ b/src/libsysprof/sysprof-bundled-symbolizer.c @@ -0,0 +1,260 @@ +/* sysprof-bundled-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-bundled-symbolizer-private.h" +#include "sysprof-document-private.h" +#include "sysprof-symbolizer-private.h" +#include "sysprof-symbol-private.h" + +struct _SysprofBundledSymbolizer +{ + SysprofSymbolizer parent_instance; + + const SysprofPackedSymbol *symbols; + guint n_symbols; + + GBytes *bytes; + const gchar *beginptr; + const gchar *endptr; +}; + +struct _SysprofBundledSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofBundledSymbolizer, sysprof_bundled_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +static void +sysprof_bundled_symbolizer_decode (SysprofBundledSymbolizer *self, + GBytes *bytes, + gboolean is_native) +{ + char *beginptr; + char *endptr; + + g_assert (SYSPROF_IS_BUNDLED_SYMBOLIZER (self)); + g_assert (bytes != NULL); + + /* Our GBytes always contain a trialing \0 after what we think + * is the end of the buffer. + */ + beginptr = (char *)g_bytes_get_data (bytes, NULL); + endptr = beginptr + g_bytes_get_size (bytes); + + for (char *ptr = beginptr; + ptr < endptr && (ptr + sizeof (SysprofPackedSymbol)) < endptr; + ptr += sizeof (SysprofPackedSymbol)) + { + SysprofPackedSymbol *sym = (SysprofPackedSymbol *)ptr; + + if (sym->addr_begin == 0 && + sym->addr_end == 0 && + sym->pid == 0 && + sym->offset == 0) + { + self->symbols = (const SysprofPackedSymbol *)beginptr; + self->n_symbols = sym - self->symbols; + break; + } + else if (!is_native) + { + sym->addr_begin = GUINT64_SWAP_LE_BE (sym->addr_begin); + sym->addr_end = GUINT64_SWAP_LE_BE (sym->addr_end); + sym->pid = GUINT32_SWAP_LE_BE (sym->pid); + sym->offset = GUINT32_SWAP_LE_BE (sym->offset); + sym->tag_offset = GUINT32_SWAP_LE_BE (sym->tag_offset); + } + } + + self->beginptr = beginptr; + self->endptr = endptr; + self->bytes = g_bytes_ref (bytes); +} + +static void +sysprof_bundled_symbolizer_prepare_async (SysprofSymbolizer *symbolizer, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SysprofBundledSymbolizer *self = (SysprofBundledSymbolizer *)symbolizer; + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GTask) task = NULL; + + g_assert (SYSPROF_IS_BUNDLED_SYMBOLIZER (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_bundled_symbolizer_prepare_async); + + if ((file = sysprof_document_lookup_file (document, "__symbols__")) && + (bytes = sysprof_document_file_dup_bytes (file))) + sysprof_bundled_symbolizer_decode (self, bytes, _sysprof_document_is_native (document)); + + g_task_return_boolean (task, TRUE); +} + +static gboolean +sysprof_bundled_symbolizer_prepare_finish (SysprofSymbolizer *symbolizer, + GAsyncResult *result, + GError **error) +{ + g_assert (SYSPROF_IS_BUNDLED_SYMBOLIZER (symbolizer)); + g_assert (G_IS_TASK (result)); + g_assert (g_task_is_valid (result, symbolizer)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static gint +search_for_symbol_cb (gconstpointer a, + gconstpointer b) +{ + const SysprofPackedSymbol *key = a; + const SysprofPackedSymbol *ele = b; + + if (key->pid < ele->pid) + return -1; + + if (key->pid > ele->pid) + return 1; + + g_assert (key->pid == ele->pid); + + if (key->addr_begin < ele->addr_begin) + return -1; + + if (key->addr_begin >= ele->addr_end) + return 1; + + g_assert (key->addr_begin >= ele->addr_begin); + g_assert (key->addr_end <= ele->addr_end); + + return 0; +} + +static SysprofSymbol * +sysprof_bundled_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofBundledSymbolizer *self = SYSPROF_BUNDLED_SYMBOLIZER (symbolizer); + g_autoptr(GRefString) tag = NULL; + const SysprofPackedSymbol *ret; + const SysprofPackedSymbol key = { + .addr_begin = address, + .addr_end = address, + .pid = process_info ? process_info->pid : 0, + .offset = 0, + .tag_offset = 0, + }; + + if (self->n_symbols == 0) + return NULL; + + g_assert (self->symbols != NULL); + g_assert (self->n_symbols > 0); + + ret = bsearch (&key, + self->symbols, + self->n_symbols, + sizeof (SysprofPackedSymbol), + search_for_symbol_cb); + + if (ret == NULL || ret->offset == 0) + return NULL; + + if (ret->tag_offset > 0) + { + if (ret->tag_offset < (self->endptr - self->beginptr)) + tag = sysprof_strings_get (strings, &self->beginptr[ret->tag_offset]); + } + + if (ret->offset < (self->endptr - self->beginptr)) + { + const char *name = &self->beginptr[ret->offset]; + SysprofSymbolKind kind; + + if (g_str_has_prefix (name, "- -")) + kind = SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH; + else if (context == SYSPROF_ADDRESS_CONTEXT_KERNEL) + kind = SYSPROF_SYMBOL_KIND_KERNEL; + else if (name[0] == '[') + kind = SYSPROF_SYMBOL_KIND_PROCESS; + else + kind = SYSPROF_SYMBOL_KIND_USER; + + return _sysprof_symbol_new (sysprof_strings_get (strings, name), + NULL, + g_steal_pointer (&tag), + ret->addr_begin, + ret->addr_end, + kind); + } + + return NULL; +} + +static void +sysprof_bundled_symbolizer_finalize (GObject *object) +{ + SysprofBundledSymbolizer *self = (SysprofBundledSymbolizer *)object; + + self->symbols = NULL; + self->n_symbols = 0; + self->beginptr = NULL; + self->endptr = NULL; + + g_clear_pointer (&self->bytes, g_bytes_unref); + + G_OBJECT_CLASS (sysprof_bundled_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_bundled_symbolizer_class_init (SysprofBundledSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->finalize = sysprof_bundled_symbolizer_finalize; + + symbolizer_class->prepare_async = sysprof_bundled_symbolizer_prepare_async; + symbolizer_class->prepare_finish = sysprof_bundled_symbolizer_prepare_finish; + symbolizer_class->symbolize = sysprof_bundled_symbolizer_symbolize; +} + +static void +sysprof_bundled_symbolizer_init (SysprofBundledSymbolizer *self) +{ +} + +SysprofSymbolizer * +sysprof_bundled_symbolizer_new (void) +{ + return g_object_new (SYSPROF_TYPE_BUNDLED_SYMBOLIZER, NULL); +} diff --git a/src/libsysprof/sysprof-bundled-symbolizer.h b/src/libsysprof/sysprof-bundled-symbolizer.h new file mode 100644 index 00000000..307d9594 --- /dev/null +++ b/src/libsysprof/sysprof-bundled-symbolizer.h @@ -0,0 +1,42 @@ +/* sysprof-bundled-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_BUNDLED_SYMBOLIZER (sysprof_bundled_symbolizer_get_type()) +#define SYSPROF_IS_BUNDLED_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_BUNDLED_SYMBOLIZER) +#define SYSPROF_BUNDLED_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_BUNDLED_SYMBOLIZER, SysprofBundledSymbolizer) +#define SYSPROF_BUNDLED_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_BUNDLED_SYMBOLIZER, SysprofBundledSymbolizerClass) + +typedef struct _SysprofBundledSymbolizer SysprofBundledSymbolizer; +typedef struct _SysprofBundledSymbolizerClass SysprofBundledSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_bundled_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_bundled_symbolizer_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofBundledSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph-categorize.c b/src/libsysprof/sysprof-callgraph-categorize.c new file mode 100644 index 00000000..293c53d5 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-categorize.c @@ -0,0 +1,54 @@ +/* sysprof-callgraph-categorize.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-callgraph-private.h" +#include "sysprof-categories-private.h" +#include "sysprof-symbol-private.h" + +SysprofCallgraphCategory +_sysprof_callgraph_node_categorize (SysprofCallgraphNode *node) +{ + SysprofSymbol *symbol; + SysprofCallgraphCategory category; + + g_return_val_if_fail (node, SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED); + g_return_val_if_fail (node->summary, SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED); + g_return_val_if_fail (node->summary->symbol, SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED); + + symbol = node->summary->symbol; + + /* NOTE: We could certainly extend this to allow users to define custom + * categorization. Additionally, we might want to match more than one node so + * that you can do valgrind style function matches like: + */ + + if (symbol->kind != SYSPROF_SYMBOL_KIND_USER || + symbol->binary_nick == NULL) + return SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED; + + category = sysprof_categories_lookup (NULL, symbol->binary_nick, symbol->name); + + if (category == 0) + return SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED; + + return category; +} diff --git a/src/libsysprof/sysprof-callgraph-frame-private.h b/src/libsysprof/sysprof-callgraph-frame-private.h new file mode 100644 index 00000000..2c58fbd2 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-frame-private.h @@ -0,0 +1,32 @@ +/* sysprof-callgraph-frame-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-callgraph.h" +#include "sysprof-callgraph-frame.h" + +G_BEGIN_DECLS + +SysprofCallgraphFrame *_sysprof_callgraph_frame_new_for_node (SysprofCallgraph *callgraph, + GObject *owner, + SysprofCallgraphNode *node); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph-frame.c b/src/libsysprof/sysprof-callgraph-frame.c new file mode 100644 index 00000000..e3d3cd45 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-frame.c @@ -0,0 +1,631 @@ +/* sysprof-callgraph-frame.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-callgraph-private.h" +#include "sysprof-callgraph-frame-private.h" +#include "sysprof-category-summary-private.h" +#include "sysprof-enums.h" +#include "sysprof-symbol-private.h" +#include "sysprof-document-bitset-index-private.h" + +#include "eggbitset.h" + +#define MAX_STACK_DEPTH 128 + +struct _SysprofCallgraphFrame +{ + GObject parent_instance; + SysprofCallgraph *callgraph; + GObject *owner; + SysprofCallgraphNode *node; + guint n_children; +}; + +enum { + PROP_0, + PROP_CALLGRAPH, + PROP_CATEGORY, + PROP_SYMBOL, + PROP_N_ITEMS, + N_PROPS +}; + +static GType +sysprof_callgraph_frame_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_CALLGRAPH_FRAME; +} + +static guint +sysprof_callgraph_frame_get_n_items (GListModel *model) +{ + return SYSPROF_CALLGRAPH_FRAME (model)->n_children; +} + +static gpointer +sysprof_callgraph_frame_get_item (GListModel *model, + guint position) +{ + SysprofCallgraphFrame *self = SYSPROF_CALLGRAPH_FRAME (model); + SysprofCallgraphNode *iter; + + if (self->callgraph == NULL) + return NULL; + + iter = self->node->children; + + while (iter != NULL && position > 0) + { + iter = iter->next; + position--; + } + + if (iter == NULL) + return NULL; + + return _sysprof_callgraph_frame_new_for_node (self->callgraph, self->owner, iter); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_callgraph_frame_get_item_type; + iface->get_n_items = sysprof_callgraph_frame_get_n_items; + iface->get_item = sysprof_callgraph_frame_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofCallgraphFrame, sysprof_callgraph_frame, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_callgraph_frame_finalize (GObject *object) +{ + SysprofCallgraphFrame *self = (SysprofCallgraphFrame *)object; + + g_clear_weak_pointer (&self->callgraph); + g_clear_object (&self->owner); + self->node = NULL; + + G_OBJECT_CLASS (sysprof_callgraph_frame_parent_class)->finalize (object); +} + +static void +sysprof_callgraph_frame_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofCallgraphFrame *self = SYSPROF_CALLGRAPH_FRAME (object); + + switch (prop_id) + { + case PROP_CALLGRAPH: + g_value_set_object (value, self->callgraph); + break; + + case PROP_CATEGORY: + g_value_set_enum (value, sysprof_callgraph_frame_get_category (self)); + break; + + case PROP_N_ITEMS: + g_value_set_uint (value, g_list_model_get_n_items (G_LIST_MODEL (self))); + break; + + case PROP_SYMBOL: + g_value_set_object (value, sysprof_callgraph_frame_get_symbol (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_callgraph_frame_class_init (SysprofCallgraphFrameClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_callgraph_frame_finalize; + object_class->get_property = sysprof_callgraph_frame_get_property; + + properties [PROP_CALLGRAPH] = + g_param_spec_object ("callgraph", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_CATEGORY] = + g_param_spec_enum ("category", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH_CATEGORY, + SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_N_ITEMS] = + g_param_spec_uint ("n-items", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SYMBOL] = + g_param_spec_object ("symbol", NULL, NULL, + SYSPROF_TYPE_SYMBOL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_callgraph_frame_init (SysprofCallgraphFrame *self) +{ +} + +SysprofCallgraphFrame * +_sysprof_callgraph_frame_new_for_node (SysprofCallgraph *callgraph, + GObject *owner, + SysprofCallgraphNode *node) +{ + SysprofCallgraphFrame *self; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (callgraph), NULL); + g_return_val_if_fail (node != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_CALLGRAPH_FRAME, NULL); + g_set_weak_pointer (&self->callgraph, callgraph); + g_set_object (&self->owner, owner); + self->node = node; + + for (const SysprofCallgraphNode *iter = node->children; + iter != NULL; + iter = iter->next) + self->n_children++; + + return self; +} + +/** + * sysprof_callgraph_frame_get_symbol: + * @self: a #SysprofCallgraphFrame + * + * Gets the symbol for the frame. + * + * Returns: (nullable) (transfer none): a #SysprofSymbol + */ +SysprofSymbol * +sysprof_callgraph_frame_get_symbol (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + + if (self->callgraph == NULL) + return NULL; + + return self->node->summary->symbol; +} + +/** + * sysprof_callgraph_frame_get_augment: (skip) + * @self: a #SysprofCallgraphFrame + * + * Gets the augmentation that was attached to the callgrpah node. + * + * Returns: (nullable) (transfer none): the augmentation data + */ +gpointer +sysprof_callgraph_frame_get_augment (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + + if (self->callgraph == NULL) + return NULL; + + return sysprof_callgraph_get_augment (self->callgraph, self->node); +} + +/** + * sysprof_callgraph_frame_get_summary_augment: (skip) + * @self: a #SysprofCallgraphFrame + * + * Gets the augmentation that was attached to the summary for + * the callgraph node's symbol. + * + * Returns: (nullable) (transfer none): the augmentation data + */ +gpointer +sysprof_callgraph_frame_get_summary_augment (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + + if (self->callgraph == NULL) + return NULL; + + return sysprof_callgraph_get_summary_augment (self->callgraph, self->node); +} + +/** + * sysprof_callgraph_frame_get_callgraph: + * @self: a #SysprofCallgraphFrame + * + * Gets the callgraph the frame belongs to. + * + * Returns: (transfer none) (nullable): a #SysprofCallgraph, or %NULL + */ +SysprofCallgraph * +sysprof_callgraph_frame_get_callgraph (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + + return self->callgraph; +} + +static gboolean +traceable_has_prefix (SysprofDocument *document, + SysprofDocumentTraceable *traceable, + GPtrArray *prefix) +{ + SysprofAddressContext final_context; + SysprofSymbol **symbols; + SysprofAddress *addresses; + guint s = 0; + guint stack_depth; + guint n_symbols; + + stack_depth = sysprof_document_traceable_get_stack_depth (traceable); + if (stack_depth > MAX_STACK_DEPTH) + return FALSE; + + addresses = g_alloca (sizeof (SysprofAddress) * stack_depth); + sysprof_document_traceable_get_stack_addresses (traceable, addresses, stack_depth); + + symbols = g_alloca (sizeof (SysprofSymbol *) * stack_depth); + n_symbols = sysprof_document_symbolize_traceable (document, traceable, symbols, stack_depth, &final_context); + + if (n_symbols < prefix->len) + return FALSE; + + for (guint p = 0; p < prefix->len; p++) + { + SysprofSymbol *prefix_symbol = g_ptr_array_index (prefix, p); + gboolean found = FALSE; + + for (; !found && s < n_symbols; s++) + found = sysprof_symbol_equal (prefix_symbol, symbols[s]); + + if (!found) + return FALSE; + } + + return TRUE; +} + +typedef struct _FilterByPrefix +{ + SysprofDocument *document; + GListModel *traceables; + GPtrArray *prefix; + EggBitset *bitset; +} FilterByPrefix; + +static void +filter_by_prefix_free (FilterByPrefix *state) +{ + g_clear_object (&state->document); + g_clear_object (&state->traceables); + g_clear_pointer (&state->prefix, g_ptr_array_unref); + g_clear_pointer (&state->bitset, egg_bitset_unref); + g_free (state); +} + +static void +filter_by_prefix_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + FilterByPrefix *state = task_data; + g_autoptr(EggBitset) bitset = NULL; + SysprofDocument *document; + GListModel *model; + GPtrArray *prefix; + EggBitsetIter iter; + guint i; + + g_assert (G_IS_TASK (task)); + g_assert (SYSPROF_IS_CALLGRAPH_FRAME (source_object)); + g_assert (state != NULL); + g_assert (G_IS_LIST_MODEL (state->traceables)); + g_assert (state->prefix != NULL); + g_assert (state->prefix->len > 0); + g_assert (state->bitset != NULL); + g_assert (!egg_bitset_is_empty (state->bitset)); + + bitset = egg_bitset_new_empty (); + + model = state->traceables; + document = state->document; + prefix = state->prefix; + + if (egg_bitset_iter_init_first (&iter, state->bitset, &i)) + { + do + { + g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (model, i); + + if (traceable_has_prefix (document, traceable, prefix)) + egg_bitset_add (bitset, i); + } + while (egg_bitset_iter_next (&iter, &i)); + } + + g_task_return_pointer (task, + _sysprof_document_bitset_index_new (model, bitset), + g_object_unref); +} + +/** + * sysprof_callgraph_frame_list_traceables: + * @self: a #SysprofCallgraphFrame + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: a #GAsyncReadyCallback + * @user_data: closure data for @callback + * + * Asynchronously lists the traceables that contain @self. + */ +void +sysprof_callgraph_frame_list_traceables_async (SysprofCallgraphFrame *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) prefix = NULL; + g_autoptr(EggBitset) bitset = NULL; + FilterByPrefix *state; + + g_return_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_callgraph_frame_list_traceables_async); + + if (self->callgraph == NULL) + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Callgraph already disposed"); + return; + } + + prefix = g_ptr_array_new (); + + for (SysprofCallgraphNode *node = self->node; + node != NULL; + node = node->parent) + { + SysprofCallgraphSummary *summary = node->summary; + SysprofSymbol *symbol = summary->symbol; + + if (symbol->kind != SYSPROF_SYMBOL_KIND_USER && + symbol->kind != SYSPROF_SYMBOL_KIND_KERNEL) + continue; + + if (bitset == NULL) + bitset = egg_bitset_copy (summary->traceables); + else + egg_bitset_intersect (bitset, summary->traceables); + + g_ptr_array_add (prefix, symbol); + } + + if (prefix->len == 0 || egg_bitset_is_empty (bitset)) + { + g_task_return_pointer (task, + g_list_store_new (SYSPROF_TYPE_DOCUMENT_TRACEABLE), + g_object_unref); + return; + } + + state = g_new0 (FilterByPrefix, 1); + state->document = g_object_ref (self->callgraph->document); + state->traceables = g_object_ref (self->callgraph->traceables); + state->prefix = g_steal_pointer (&prefix); + state->bitset = egg_bitset_ref (bitset); + + g_task_set_task_data (task, state, (GDestroyNotify)filter_by_prefix_free); + g_task_run_in_thread (task, filter_by_prefix_worker); +} + +/** + * sysprof_callgraph_frame_list_traceables_finish: + * @self: a #SysprofCallgraphFrame + * + * Completes an asynchronous request to list traceables. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentTraceable if + * successful; otherwise %NULL and @error is set. + */ +GListModel * +sysprof_callgraph_frame_list_traceables_finish (SysprofCallgraphFrame *self, + GAsyncResult *result, + GError **error) +{ + GListModel *ret; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + ret = g_task_propagate_pointer (G_TASK (result), error); + + g_return_val_if_fail (!ret || G_IS_LIST_MODEL (ret), NULL); + + return ret; +} + +gboolean +sysprof_callgraph_frame_is_leaf (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), 0); + + return self->n_children == 0; +} + +/** + * sysprof_callgraph_frame_get_category: + * @self: a #SysprofCallgraphFrame + * + * Gets the category of the node if %SYSPROF_CALLGRAPH_FLAGS_CATEGORIZE_FRAMES + * was set when generating the callgraph. Otherwise + * %SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED. + * + * Returns: callgraph category + */ +SysprofCallgraphCategory +sysprof_callgraph_frame_get_category (SysprofCallgraphFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), 0); + + if (self->callgraph != NULL && self->node != NULL && self->node->category) + return self->node->category & ~SYSPROF_CALLGRAPH_CATEGORY_INHERIT; + + return SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED; +} + +typedef struct _Summary +{ + guint64 count; +} Summary; + +static void +summarize_node (const SysprofCallgraphNode *node, + Summary *summaries) +{ + guint category = SYSPROF_CALLGRAPH_CATEGORY_UNMASK(node->category); + + if (node->is_toplevel && + category != 0 && + category != SYSPROF_CALLGRAPH_CATEGORY_PRESENTATION) + { + gboolean seen[SYSPROF_CALLGRAPH_CATEGORY_LAST] = {0}; + + /* Track total count in 0 */ + summaries[0].count += node->count; + + seen[category] = TRUE; + summaries[category].count += node->count; + + for (const SysprofCallgraphNode *parent = node->parent; parent; parent = parent->parent) + { + guint parent_category = SYSPROF_CALLGRAPH_CATEGORY_UNMASK (parent->category); + + if (!seen[parent_category] && (parent->category & SYSPROF_CALLGRAPH_CATEGORY_INHERIT) != 0) + { + seen[parent_category] = TRUE; + summaries[parent_category].count += node->count; + } + } + } + + for (const SysprofCallgraphNode *iter = node->children; iter; iter = iter->next) + summarize_node (iter, summaries); +} + +static void +sysprof_callgraph_frame_summarize (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofCallgraphFrame *self = source_object; + g_autofree Summary *summaries = NULL; + g_autoptr(GListStore) store = NULL; + guint64 total = 0; + + g_assert (G_IS_TASK (task)); + g_assert (SYSPROF_IS_CALLGRAPH_FRAME (self)); + g_assert (SYSPROF_IS_CALLGRAPH (task_data)); + + summaries = g_new0 (Summary, SYSPROF_CALLGRAPH_CATEGORY_LAST); + summarize_node (self->node, summaries); + + store = g_list_store_new (G_TYPE_OBJECT); + total = summaries[0].count; + + for (guint i = 1; i < SYSPROF_CALLGRAPH_CATEGORY_LAST; i++) + { + g_autoptr(SysprofCategorySummary) summary = NULL; + + if (summaries[i].count == 0) + continue; + + summary = g_object_new (SYSPROF_TYPE_CATEGORY_SUMMARY, NULL); + summary->category = i; + summary->total = total; + summary->count = summaries[i].count; + + g_list_store_append (store, summary); + } + + g_task_return_pointer (task, g_steal_pointer (&store), g_object_unref); +} + +void +sysprof_callgraph_frame_summarize_async (SysprofCallgraphFrame *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_callgraph_frame_summarize_async); + + if (self->callgraph == NULL) + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_FAILED, + "Callgraph disposed"); + return; + } + + g_task_set_task_data (task, g_object_ref (self->callgraph), g_object_unref); + g_task_run_in_thread (task, sysprof_callgraph_frame_summarize); +} + +/** + * sysprof_callgraph_frame_summarize_finish: + * + * Returns: (transfer full): a #GListModel of #SysprofCategorySummary + */ +GListModel * +sysprof_callgraph_frame_summarize_finish (SysprofCallgraphFrame *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_FRAME (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/libsysprof/sysprof-callgraph-frame.h b/src/libsysprof/sysprof-callgraph-frame.h new file mode 100644 index 00000000..3e173392 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-frame.h @@ -0,0 +1,63 @@ +/* sysprof-callgraph-frame.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-symbol.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CALLGRAPH_FRAME (sysprof_callgraph_frame_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofCallgraphFrame, sysprof_callgraph_frame, SYSPROF, CALLGRAPH_FRAME, GObject) + +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbol *sysprof_callgraph_frame_get_symbol (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_frame_get_augment (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_frame_get_summary_augment (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_callgraph_frame_list_traceables_async (SysprofCallgraphFrame *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_frame_list_traceables_finish (SysprofCallgraphFrame *self, + GAsyncResult *result, + GError **error); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_callgraph_frame_is_leaf (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_callgraph_frame_summarize_async (SysprofCallgraphFrame *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_frame_summarize_finish (SysprofCallgraphFrame *self, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph-private.h b/src/libsysprof/sysprof-callgraph-private.h new file mode 100644 index 00000000..5e588be9 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-private.h @@ -0,0 +1,96 @@ +/* sysprof-callgraph-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-callgraph.h" +#include "sysprof-document.h" + +#include "eggbitset.h" + +G_BEGIN_DECLS + +#define SYSPROF_CALLGRAPH_CATEGORY_INHERIT (1 << 6) +#define SYSPROF_CALLGRAPH_CATEGORY_UNMASK(c) (c & ~(SYSPROF_CALLGRAPH_CATEGORY_INHERIT)) + +typedef struct _SysprofCallgraphSummary +{ + SysprofSymbol *symbol; + EggBitset *traceables; + GPtrArray *callers; + gpointer augment[2]; +} SysprofCallgraphSummary; + +struct _SysprofCallgraphNode +{ + SysprofCallgraphNode *parent; + SysprofCallgraphNode *prev; + SysprofCallgraphNode *next; + SysprofCallgraphNode *children; + SysprofCallgraphSummary *summary; + gpointer augment[2]; + guint category : 7; + guint is_toplevel : 1; + guint count : 24; +}; + +struct _SysprofCallgraph +{ + GObject parent_instance; + + SysprofDocument *document; + GListModel *traceables; + + GHashTable *symbol_to_summary; + GPtrArray *symbols; + + SysprofCallgraphFlags flags; + + gsize augment_size; + SysprofAugmentationFunc augment_func; + gpointer augment_func_data; + GDestroyNotify augment_func_data_destroy; + + SysprofCallgraphNode root; +}; + +void _sysprof_callgraph_new_async (SysprofDocument *document, + SysprofCallgraphFlags flags, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SysprofCallgraph *_sysprof_callgraph_new_finish (GAsyncResult *result, + GError **error); +gpointer _sysprof_callgraph_get_symbol_augment (SysprofCallgraph *self, + SysprofSymbol *symbol); +void _sysprof_callgraph_node_free (SysprofCallgraphNode *self, + gboolean free_self); +SysprofCallgraphCategory _sysprof_callgraph_node_categorize (SysprofCallgraphNode *node); +void _sysprof_callgraph_categorize (SysprofCallgraph *self, + SysprofCallgraphNode *node); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph-profile.c b/src/libsysprof/sysprof-callgraph-profile.c deleted file mode 100644 index 2991202b..00000000 --- a/src/libsysprof/sysprof-callgraph-profile.c +++ /dev/null @@ -1,551 +0,0 @@ -/* sysprof-callgraph-profile.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2009-2012 Soeren Sandmann and others - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "../stackstash.h" - -#include "sysprof-capture-util-private.h" - -#include "sysprof-callgraph-profile.h" -#include "sysprof-capture-reader.h" -#include "sysprof-capture-symbol-resolver.h" -#include "sysprof-elf-symbol-resolver.h" -#include "sysprof-jitmap-symbol-resolver.h" -#include "sysprof-kernel-symbol-resolver.h" -#include "sysprof-map-lookaside.h" -#include "sysprof-selection.h" - -#define CHECK_CANCELLABLE_INTERVAL 100 - -struct _SysprofCallgraphProfile -{ - GObject parent_instance; - - SysprofCaptureReader *reader; - SysprofSelection *selection; - StackStash *stash; - GStringChunk *symbols; - GHashTable *tags; -}; - -typedef struct -{ - SysprofCaptureReader *reader; - SysprofSelection *selection; -} Generate; - -static void profile_iface_init (SysprofProfileInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofCallgraphProfile, sysprof_callgraph_profile, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_PROFILE, profile_iface_init)) - -enum { - PROP_0, - PROP_SELECTION, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -SysprofProfile * -sysprof_callgraph_profile_new (void) -{ - return g_object_new (SYSPROF_TYPE_CALLGRAPH_PROFILE, NULL); -} - -SysprofProfile * -sysprof_callgraph_profile_new_with_selection (SysprofSelection *selection) -{ - return g_object_new (SYSPROF_TYPE_CALLGRAPH_PROFILE, - "selection", selection, - NULL); -} - -static void -sysprof_callgraph_profile_finalize (GObject *object) -{ - SysprofCallgraphProfile *self = (SysprofCallgraphProfile *)object; - - g_clear_pointer (&self->symbols, g_string_chunk_free); - g_clear_pointer (&self->stash, stack_stash_unref); - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - g_clear_pointer (&self->tags, g_hash_table_unref); - g_clear_object (&self->selection); - - G_OBJECT_CLASS (sysprof_callgraph_profile_parent_class)->finalize (object); -} - -static void -sysprof_callgraph_profile_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofCallgraphProfile *self = SYSPROF_CALLGRAPH_PROFILE (object); - - switch (prop_id) - { - case PROP_SELECTION: - g_value_set_object (value, self->selection); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_callgraph_profile_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofCallgraphProfile *self = SYSPROF_CALLGRAPH_PROFILE (object); - - switch (prop_id) - { - case PROP_SELECTION: - self->selection = g_value_dup_object (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_callgraph_profile_class_init (SysprofCallgraphProfileClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_callgraph_profile_finalize; - object_class->get_property = sysprof_callgraph_profile_get_property; - object_class->set_property = sysprof_callgraph_profile_set_property; - - properties [PROP_SELECTION] = - g_param_spec_object ("selection", - "Selection", - "The selection for filtering the callgraph", - SYSPROF_TYPE_SELECTION, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_callgraph_profile_init (SysprofCallgraphProfile *self) -{ - self->symbols = g_string_chunk_new (_sysprof_getpagesize ()); - self->tags = g_hash_table_new (g_str_hash, g_str_equal); -} - -static void -sysprof_callgraph_profile_set_reader (SysprofProfile *profile, - SysprofCaptureReader *reader) -{ - SysprofCallgraphProfile *self = (SysprofCallgraphProfile *)profile; - - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (self)); - g_assert (reader != NULL); - - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - self->reader = sysprof_capture_reader_ref (reader); -} - -static const gchar * -sysprof_callgraph_profile_intern_string_take (SysprofCallgraphProfile *self, - gchar *str) -{ - const gchar *ret; - - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (self)); - g_assert (str != NULL); - - ret = g_string_chunk_insert_const (self->symbols, str); - g_free (str); - return ret; -} - -static const gchar * -sysprof_callgraph_profile_intern_string (SysprofCallgraphProfile *self, - const gchar *str) -{ - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (self)); - g_assert (str != NULL); - - return g_string_chunk_insert_const (self->symbols, str); -} - -static void -sysprof_callgraph_profile_generate_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofCallgraphProfile *self = source_object; - Generate *gen = task_data; - SysprofCaptureReader *reader; - SysprofSelection *selection; - g_autoptr(GArray) resolved = NULL; - g_autoptr(GHashTable) maps_by_pid = NULL; - g_autoptr(GHashTable) cmdlines = NULL; - g_autoptr(GPtrArray) resolvers = NULL; - SysprofCaptureFrameType type; - StackStash *stash = NULL; - StackStash *resolved_stash = NULL; - guint count = 0; - gboolean ret = FALSE; - - g_assert (G_IS_TASK (task)); - g_assert (gen != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - reader = gen->reader; - selection = gen->selection; - - maps_by_pid = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sysprof_map_lookaside_free); - cmdlines = g_hash_table_new (NULL, NULL); - - stash = stack_stash_new (NULL); - resolved_stash = stack_stash_new (NULL); - - resolvers = g_ptr_array_new_with_free_func (g_object_unref); - g_ptr_array_add (resolvers, sysprof_capture_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_kernel_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_elf_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_jitmap_symbol_resolver_new ()); - - for (guint j = 0; j < resolvers->len; j++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (resolvers, j); - - sysprof_capture_reader_reset (reader); - sysprof_symbol_resolver_load (resolver, reader); - } - - sysprof_capture_reader_reset (reader); - - /* - * The resolved pointer array is where we stash the names for the - * instruction pointers to pass to the stash stack. All the strings - * need to be deduplicated so that pointer comparison works as if we - * did instruction-pointer comparison. - */ - resolved = g_array_new (FALSE, TRUE, sizeof (guint64)); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - const SysprofCaptureProcess *pr; - - if (type != SYSPROF_CAPTURE_FRAME_PROCESS) - { - if (!sysprof_capture_reader_skip (reader)) - goto failure; - continue; - } - - if (NULL == (pr = sysprof_capture_reader_read_process (reader))) - goto failure; - - if (!g_hash_table_contains (cmdlines, GINT_TO_POINTER (pr->frame.pid))) - { - g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline); - g_hash_table_insert (cmdlines, - GINT_TO_POINTER (pr->frame.pid), - (gchar *)sysprof_callgraph_profile_intern_string (self, cmdline)); - } - } - - if (g_task_return_error_if_cancelled (task)) - goto cleanup; - - sysprof_capture_reader_reset (reader); - - /* - * Walk through all of the sample events and resolve instruction-pointers - * to symbol names by loading the particular map and extracting the symbol - * name. If we wanted to support dynamic systems, we'd want to extend this - * to parse information from captured data about the languages jit'd code. - */ - while (sysprof_capture_reader_peek_type (reader, &type)) - { - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - const SysprofCaptureSample *sample; - StackNode *node; - StackNode *iter; - const gchar *cmdline; - guint len = 5; - - if (type != SYSPROF_CAPTURE_FRAME_SAMPLE) - { - if (!sysprof_capture_reader_skip (reader)) - goto failure; - continue; - } - - if (++count == CHECK_CANCELLABLE_INTERVAL) - { - if (g_task_return_error_if_cancelled (task)) - goto cleanup; - } - - if (NULL == (sample = sysprof_capture_reader_read_sample (reader))) - goto failure; - - if (!sysprof_selection_contains (selection, sample->frame.time)) - continue; - - if (sample->n_addrs == 0) - continue; - - cmdline = g_hash_table_lookup (cmdlines, GINT_TO_POINTER (sample->frame.pid)); - - if (cmdline == NULL) - { - gchar *pidstr = g_strdup_printf ("[Process %d]", sample->frame.pid); - g_hash_table_insert (cmdlines, GINT_TO_POINTER (sample->frame.pid), pidstr); - cmdline = pidstr; - } - -#if 0 - /* This assertion appears to hold true, but since we're taking in - * untrusted data from capture files, it's not safe to assume. But in - * practice it is. - */ - g_assert (sysprof_address_is_context_switch (sample->addrs[0], &last_context)); - last_context = SYSPROF_ADDRESS_CONTEXT_NONE; -#endif - - node = stack_stash_add_trace (stash, (gpointer)sample->addrs, sample->n_addrs, 1); - - for (iter = node; iter != NULL; iter = iter->parent) - len++; - - if (G_UNLIKELY (resolved->len < len)) - g_array_set_size (resolved, len); - - len = 0; - - for (iter = node; iter != NULL; iter = iter->parent) - { - SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; - SysprofAddress address = iter->data; - const gchar *symbol = NULL; - - if (sysprof_address_is_context_switch (address, &context)) - { - if (last_context) - symbol = sysprof_address_context_to_string (last_context); - else - symbol = NULL; - - last_context = context; - } - else - { - /* In case we get plain backtraces that aren't coming from perf, - * we might never get a context switch into user-space. This ensures - * that we still get traces for things from backtrace(). - */ - if (last_context == SYSPROF_ADDRESS_CONTEXT_NONE) - last_context = SYSPROF_ADDRESS_CONTEXT_USER; - - for (guint j = 0; j < resolvers->len; j++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (resolvers, j); - GQuark tag = 0; - gchar *str; - - str = sysprof_symbol_resolver_resolve_with_context (resolver, - sample->frame.time, - sample->frame.pid, - last_context, - address, - &tag); - - if (str != NULL) - { - symbol = sysprof_callgraph_profile_intern_string_take (self, str); - if (tag != 0) - g_hash_table_insert (self->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag)); - break; - } - } - } - - if (symbol != NULL) - g_array_index (resolved, SysprofAddress, len++) = POINTER_TO_U64 (symbol); - } - - if (last_context && last_context != SYSPROF_ADDRESS_CONTEXT_USER) - { - /* Kernel threads do not have a user part, so we end up here - * without ever getting a user context. If this happens, - * add the '- - kernel - - ' name, so that kernel threads - * are properly blamed on the kernel - */ - const gchar *name = sysprof_address_context_to_string (last_context); - g_array_index (resolved, SysprofAddress, len++) = POINTER_TO_U64 (name); - } - - g_array_index (resolved, guint64, len++) = POINTER_TO_U64 (cmdline); - g_array_index (resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]"); - - stack_stash_add_trace (resolved_stash, (gpointer)resolved->data, len, 1); - } - - ret = TRUE; - -failure: - - if (ret == FALSE) - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "%s", - _("Sysprof was unable to generate a callgraph from the system capture.")); - else - g_task_return_pointer (task, g_steal_pointer (&resolved_stash), (GDestroyNotify)stack_stash_unref); - -cleanup: - g_clear_pointer (&resolved_stash, stack_stash_unref); - g_clear_pointer (&stash, stack_stash_unref); -} - -static void -generate_free (Generate *generate) -{ - sysprof_capture_reader_unref (generate->reader); - g_clear_object (&generate->selection); - g_slice_free (Generate, generate); -} - -static void -sysprof_callgraph_profile_generate (SysprofProfile *profile, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofCallgraphProfile *self = (SysprofCallgraphProfile *)profile; - Generate *gen; - - g_autoptr(GTask) task = NULL; - - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (self)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - gen = g_slice_new0 (Generate); - gen->reader = sysprof_capture_reader_copy (self->reader); - gen->selection = sysprof_selection_copy (self->selection); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_task_data (task, gen, (GDestroyNotify)generate_free); - g_task_run_in_thread (task, sysprof_callgraph_profile_generate_worker); -} - -static gboolean -sysprof_callgraph_profile_generate_finish (SysprofProfile *profile, - GAsyncResult *result, - GError **error) -{ - SysprofCallgraphProfile *self = (SysprofCallgraphProfile *)profile; - StackStash *stash; - - g_assert (SYSPROF_IS_CALLGRAPH_PROFILE (self)); - g_assert (G_IS_TASK (result)); - - stash = g_task_propagate_pointer (G_TASK (result), error); - - if (stash != NULL) - { - if (stash != self->stash) - { - g_clear_pointer (&self->stash, stack_stash_unref); - self->stash = g_steal_pointer (&stash); - } - - g_clear_pointer (&stash, stack_stash_unref); - - return TRUE; - } - - return FALSE; -} - -static void -profile_iface_init (SysprofProfileInterface *iface) -{ - iface->generate = sysprof_callgraph_profile_generate; - iface->generate_finish = sysprof_callgraph_profile_generate_finish; - iface->set_reader = sysprof_callgraph_profile_set_reader; -} - -gpointer -sysprof_callgraph_profile_get_stash (SysprofCallgraphProfile *self) -{ - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PROFILE (self), NULL); - - return self->stash; -} - -gboolean -sysprof_callgraph_profile_is_empty (SysprofCallgraphProfile *self) -{ - StackNode *root; - - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PROFILE (self), FALSE); - - return (self->stash == NULL || - !(root = stack_stash_get_root (self->stash)) || - !root->total); -} - -GQuark -sysprof_callgraph_profile_get_tag (SysprofCallgraphProfile *self, - const gchar *symbol) -{ - g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_PROFILE (self), 0); - - return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tags, symbol)); -} diff --git a/src/libsysprof/sysprof-callgraph-profile.h b/src/libsysprof/sysprof-callgraph-profile.h deleted file mode 100644 index 94ed4607..00000000 --- a/src/libsysprof/sysprof-callgraph-profile.h +++ /dev/null @@ -1,51 +0,0 @@ -/* sysprof-callgraph-profile.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-version-macros.h" - -#include "sysprof-profile.h" -#include "sysprof-selection.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CALLGRAPH_PROFILE (sysprof_callgraph_profile_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofCallgraphProfile, sysprof_callgraph_profile, SYSPROF, CALLGRAPH_PROFILE, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofProfile *sysprof_callgraph_profile_new (void); -SYSPROF_AVAILABLE_IN_ALL -SysprofProfile *sysprof_callgraph_profile_new_with_selection (SysprofSelection *selection); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_callgraph_profile_is_empty (SysprofCallgraphProfile *self); -SYSPROF_AVAILABLE_IN_ALL -gpointer sysprof_callgraph_profile_get_stash (SysprofCallgraphProfile *self); -SYSPROF_AVAILABLE_IN_ALL -GQuark sysprof_callgraph_profile_get_tag (SysprofCallgraphProfile *self, - const gchar *symbol); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph-symbol-private.h b/src/libsysprof/sysprof-callgraph-symbol-private.h new file mode 100644 index 00000000..5fb61cf0 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-symbol-private.h @@ -0,0 +1,30 @@ +/* sysprof-callgraph-symbol-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-callgraph.h" + +G_BEGIN_DECLS + +GListModel *_sysprof_callgraph_symbol_list_model_new (SysprofCallgraph *callgraph, + GPtrArray *symbols); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph-symbol.c b/src/libsysprof/sysprof-callgraph-symbol.c new file mode 100644 index 00000000..5d7fab42 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph-symbol.c @@ -0,0 +1,265 @@ +/* sysprof-callgraph-symbol.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-callgraph-private.h" +#include "sysprof-callgraph-symbol-private.h" + +struct _SysprofCallgraphSymbol +{ + GObject parent_instance; + SysprofCallgraph *callgraph; + SysprofSymbol *symbol; +}; + +enum { + PROP_0, + PROP_CALLGRAPH, + PROP_SYMBOL, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofCallgraphSymbol, sysprof_callgraph_symbol, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_callgraph_symbol_finalize (GObject *object) +{ + SysprofCallgraphSymbol *self = (SysprofCallgraphSymbol *)object; + + g_clear_weak_pointer (&self->callgraph); + g_clear_object (&self->symbol); + + G_OBJECT_CLASS (sysprof_callgraph_symbol_parent_class)->finalize (object); +} + +static void +sysprof_callgraph_symbol_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofCallgraphSymbol *self = SYSPROF_CALLGRAPH_SYMBOL (object); + + switch (prop_id) + { + case PROP_CALLGRAPH: + g_value_set_object (value, self->callgraph); + break; + + case PROP_SYMBOL: + g_value_set_object (value, sysprof_callgraph_symbol_get_symbol (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_callgraph_symbol_class_init (SysprofCallgraphSymbolClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_callgraph_symbol_finalize; + object_class->get_property = sysprof_callgraph_symbol_get_property; + + properties [PROP_CALLGRAPH] = + g_param_spec_object ("callgraph", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SYMBOL] = + g_param_spec_object ("symbol", NULL, NULL, + SYSPROF_TYPE_SYMBOL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_callgraph_symbol_init (SysprofCallgraphSymbol *self) +{ +} + +SysprofCallgraphSymbol * +_sysprof_callgraph_symbol_new (SysprofCallgraph *callgraph, + SysprofSymbol *symbol) +{ + SysprofCallgraphSymbol *self; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (callgraph), NULL); + g_return_val_if_fail (SYSPROF_IS_SYMBOL (symbol), NULL); + + self = g_object_new (SYSPROF_TYPE_CALLGRAPH_SYMBOL, NULL); + g_set_weak_pointer (&self->callgraph, callgraph); + g_set_object (&self->symbol, symbol); + + return self; +} + +/** + * sysprof_callgraph_symbol_get_symbol: + * @self: a #SysprofCallgraphSymbol + * + * Gets the symbol for the symbol. + * + * Returns: (nullable) (transfer none): a #SysprofSymbol + */ +SysprofSymbol * +sysprof_callgraph_symbol_get_symbol (SysprofCallgraphSymbol *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_SYMBOL (self), NULL); + + return self->symbol; +} + +/** + * sysprof_callgraph_symbol_get_summary_augment: (skip) + * @self: a #SysprofCallgraphSymbol + * + * Gets the augmentation that was attached to the summary for + * the callgraph node's symbol. + * + * Returns: (nullable) (transfer none): the augmentation data + */ +gpointer +sysprof_callgraph_symbol_get_summary_augment (SysprofCallgraphSymbol *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_SYMBOL (self), NULL); + + if (self->callgraph == NULL) + return NULL; + + return _sysprof_callgraph_get_symbol_augment (self->callgraph, self->symbol); +} + +/** + * sysprof_callgraph_symbol_get_callgraph: + * @self: a #SysprofCallgraphSymbol + * + * Gets the callgraph the symbol belongs to. + * + * Returns: (transfer none) (nullable): a #SysprofCallgraph, or %NULL + */ +SysprofCallgraph * +sysprof_callgraph_symbol_get_callgraph (SysprofCallgraphSymbol *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_SYMBOL (self), NULL); + + return self->callgraph; +} + +typedef struct _SysprofCallgraphSymbolListModel +{ + GObject parent_instance; + SysprofCallgraph *callgraph; + GPtrArray *symbols; +} SysprofCallgraphSymbolListModel; + +static guint +sysprof_callgraph_symbol_list_model_get_n_items (GListModel *model) +{ + SysprofCallgraphSymbolListModel *self = (SysprofCallgraphSymbolListModel *)model; + + if (self->symbols != NULL) + return self->symbols->len; + + return 0; +} + +static GType +sysprof_callgraph_symbol_list_model_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_CALLGRAPH_SYMBOL; +} + +static gpointer +sysprof_callgraph_symbol_list_model_get_item (GListModel *model, + guint position) +{ + SysprofCallgraphSymbolListModel *self = (SysprofCallgraphSymbolListModel *)model; + + if (self->symbols == NULL || position >= self->symbols->len || self->callgraph == NULL) + return NULL; + + return _sysprof_callgraph_symbol_new (self->callgraph, + g_ptr_array_index (self->symbols, position)); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_n_items = sysprof_callgraph_symbol_list_model_get_n_items; + iface->get_item_type = sysprof_callgraph_symbol_list_model_get_item_type; + iface->get_item = sysprof_callgraph_symbol_list_model_get_item; +} + +#define SYSPROF_TYPE_CALLGRAPH_SYMBOL_LIST_MODEL (sysprof_callgraph_symbol_list_model_get_type()) +G_DECLARE_FINAL_TYPE (SysprofCallgraphSymbolListModel, sysprof_callgraph_symbol_list_model, SYSPROF, CALLGRAPH_SYMBOL_LIST_MODEL, GObject) +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofCallgraphSymbolListModel, sysprof_callgraph_symbol_list_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static void +sysprof_callgraph_symbol_list_model_dispose (GObject *object) +{ + SysprofCallgraphSymbolListModel *self = (SysprofCallgraphSymbolListModel *)object; + + g_clear_pointer (&self->symbols, g_ptr_array_unref); + g_clear_weak_pointer (&self->callgraph); + + G_OBJECT_CLASS (sysprof_callgraph_symbol_parent_class)->dispose (object); +} + +static void +sysprof_callgraph_symbol_list_model_class_init (SysprofCallgraphSymbolListModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_callgraph_symbol_list_model_dispose; +} + +static void +sysprof_callgraph_symbol_list_model_init (SysprofCallgraphSymbolListModel *self) +{ +} + +GListModel * +_sysprof_callgraph_symbol_list_model_new (SysprofCallgraph *callgraph, + GPtrArray *symbols) +{ + SysprofCallgraphSymbolListModel *self; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (callgraph), NULL); + + self = g_object_new (SYSPROF_TYPE_CALLGRAPH_SYMBOL_LIST_MODEL, NULL); + g_set_weak_pointer (&self->callgraph, callgraph); + + if (symbols != NULL) + self->symbols = g_ptr_array_ref (symbols); + + return G_LIST_MODEL (self); +} + +G_END_DECLS diff --git a/src/libsysprof/sysprof-proc-source.h b/src/libsysprof/sysprof-callgraph-symbol.h similarity index 58% rename from src/libsysprof/sysprof-proc-source.h rename to src/libsysprof/sysprof-callgraph-symbol.h index f3793672..a6360fbd 100644 --- a/src/libsysprof/sysprof-proc-source.h +++ b/src/libsysprof/sysprof-callgraph-symbol.h @@ -1,6 +1,6 @@ -/* sysprof-proc-source.h +/* sysprof-callgraph-symbol.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,22 +20,22 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif +#include -#include "sysprof-version-macros.h" +#include -#include "sysprof-source.h" +#include "sysprof-symbol.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_PROC_SOURCE (sysprof_proc_source_get_type()) +#define SYSPROF_TYPE_CALLGRAPH_SYMBOL (sysprof_callgraph_symbol_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofProcSource, sysprof_proc_source, SYSPROF, PROC_SOURCE, GObject) +G_DECLARE_FINAL_TYPE (SysprofCallgraphSymbol, sysprof_callgraph_symbol, SYSPROF, CALLGRAPH_SYMBOL, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_proc_source_new (void); +SysprofSymbol *sysprof_callgraph_symbol_get_symbol (SysprofCallgraphSymbol *self); +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_symbol_get_summary_augment (SysprofCallgraphSymbol *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-callgraph.c b/src/libsysprof/sysprof-callgraph.c new file mode 100644 index 00000000..20b020ea --- /dev/null +++ b/src/libsysprof/sysprof-callgraph.c @@ -0,0 +1,723 @@ +/* sysprof-callgraph.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-callgraph-private.h" +#include "sysprof-callgraph-frame-private.h" +#include "sysprof-callgraph-symbol-private.h" +#include "sysprof-descendants-model-private.h" +#include "sysprof-document-bitset-index-private.h" +#include "sysprof-document-private.h" +#include "sysprof-document-traceable.h" +#include "sysprof-symbol-private.h" + +#include "eggbitset.h" + +#define MAX_STACK_DEPTH 1024 +#define INLINE_AUGMENT_SIZE (GLIB_SIZEOF_VOID_P*2) + +static GType +sysprof_callgraph_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_CALLGRAPH_FRAME; +} + +static guint +sysprof_callgraph_get_n_items (GListModel *model) +{ + return 1; +} + +static gpointer +sysprof_callgraph_get_item (GListModel *model, + guint position) +{ + SysprofCallgraph *self = SYSPROF_CALLGRAPH (model); + + if (position > 0) + return NULL; + + return _sysprof_callgraph_frame_new_for_node (self, NULL, &self->root); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_callgraph_get_item_type; + iface->get_n_items = sysprof_callgraph_get_n_items; + iface->get_item = sysprof_callgraph_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofCallgraph, sysprof_callgraph, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static SysprofSymbol *everything; +static SysprofSymbol *untraceable; + +static void +sysprof_callgraph_summary_free_all (SysprofCallgraphSummary *summary) +{ + g_clear_pointer (&summary->augment[0], g_free); + summary->augment[1] = NULL; + g_clear_pointer (&summary->callers, g_ptr_array_unref); + g_clear_pointer (&summary->traceables, egg_bitset_unref); + g_free (summary); +} + +static void +sysprof_callgraph_summary_free_self (SysprofCallgraphSummary *summary) +{ + summary->augment[0] = NULL; + summary->augment[1] = NULL; + g_clear_pointer (&summary->callers, g_ptr_array_unref); + g_clear_pointer (&summary->traceables, egg_bitset_unref); + g_free (summary); +} + +static inline SysprofCallgraphSummary * +sysprof_callgraph_get_summary (SysprofCallgraph *self, + SysprofSymbol *symbol) +{ + SysprofCallgraphSummary *summary; + + if G_UNLIKELY (!(summary = g_hash_table_lookup (self->symbol_to_summary, symbol))) + { + summary = g_new0 (SysprofCallgraphSummary, 1); + summary->traceables = egg_bitset_new_empty (); + summary->callers = g_ptr_array_new (); + summary->symbol = symbol; + + g_hash_table_insert (self->symbol_to_summary, symbol, summary); + g_ptr_array_add (self->symbols, symbol); + } + + return summary; +} +void +_sysprof_callgraph_node_free (SysprofCallgraphNode *node, + gboolean free_self) +{ + SysprofCallgraphNode *iter = node->children; + + while (iter) + { + SysprofCallgraphNode *to_free = iter; + iter = iter->next; + _sysprof_callgraph_node_free (to_free, TRUE); + } + + if (free_self) + g_free (node); +} + +static void +sysprof_callgraph_dispose (GObject *object) +{ + SysprofCallgraph *self = (SysprofCallgraph *)object; + GDestroyNotify notify = self->augment_func_data_destroy; + gpointer notify_data = self->augment_func_data; + + self->augment_size = 0; + self->augment_func = NULL; + self->augment_func_data = NULL; + self->augment_func_data_destroy = NULL; + + if (notify != NULL) + notify (notify_data); + + G_OBJECT_CLASS (sysprof_callgraph_parent_class)->dispose (object); +} + +static void +sysprof_callgraph_finalize (GObject *object) +{ + SysprofCallgraph *self = (SysprofCallgraph *)object; + + g_clear_pointer (&self->symbol_to_summary, g_hash_table_unref); + g_clear_pointer (&self->symbols, g_ptr_array_unref); + + g_clear_object (&self->document); + g_clear_object (&self->traceables); + + _sysprof_callgraph_node_free (&self->root, FALSE); + + G_OBJECT_CLASS (sysprof_callgraph_parent_class)->finalize (object); +} + +static void +sysprof_callgraph_class_init (SysprofCallgraphClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_callgraph_dispose; + object_class->finalize = sysprof_callgraph_finalize; + + everything = _sysprof_symbol_new (g_ref_string_new_intern ("All Processes"), + NULL, NULL, 0, 0, + SYSPROF_SYMBOL_KIND_ROOT); + untraceable = _sysprof_symbol_new (g_ref_string_new_intern ("Unwindable"), + NULL, NULL, 0, 0, + SYSPROF_SYMBOL_KIND_UNWINDABLE); +} + +static void +sysprof_callgraph_init (SysprofCallgraph *self) +{ +} + +static void +sysprof_callgraph_populate_callers (SysprofCallgraph *self, + SysprofCallgraphNode *node, + guint list_model_index) +{ + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (node != NULL); + + for (const SysprofCallgraphNode *iter = node; + iter != NULL; + iter = iter->parent) + { + egg_bitset_add (iter->summary->traceables, list_model_index); + + if (iter->parent != NULL) + { + SysprofSymbol *parent_symbol = iter->parent->summary->symbol; + SysprofSymbolKind parent_kind = sysprof_symbol_get_kind (parent_symbol); + guint pos; + + if (parent_kind != SYSPROF_SYMBOL_KIND_PROCESS && + parent_kind != SYSPROF_SYMBOL_KIND_ROOT && + !g_ptr_array_find (iter->summary->callers, parent_symbol, &pos)) + g_ptr_array_add (iter->summary->callers, parent_symbol); + } + } +} + +static SysprofCallgraphNode * +sysprof_callgraph_add_trace (SysprofCallgraph *self, + SysprofSymbol **symbols, + guint n_symbols, + guint list_model_index, + gboolean hide_system_libraries) +{ + SysprofCallgraphNode *parent = NULL; + + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (n_symbols >= 2); + g_assert (symbols[n_symbols-1] == everything); + + parent = &self->root; + + for (guint i = n_symbols - 1; i > 0; i--) + { + SysprofSymbol *symbol = symbols[i-1]; + SysprofCallgraphNode *node = NULL; + + if (hide_system_libraries && _sysprof_symbol_is_system_library (symbol)) + continue; + + /* Try to find @symbol within the children of @parent */ + for (SysprofCallgraphNode *iter = parent->children; + iter != NULL; + iter = iter->next) + { + g_assert (iter != NULL); + g_assert (iter->summary != NULL); + g_assert (iter->summary->symbol != NULL); + g_assert (symbol != NULL); + + if (_sysprof_symbol_equal (iter->summary->symbol, symbol)) + { + node = iter; + goto next_symbol; + } + } + + /* Otherwise create a new node */ + node = g_new0 (SysprofCallgraphNode, 1); + node->summary = sysprof_callgraph_get_summary (self, symbol); + node->parent = parent; + node->next = parent->children; + if (parent->children) + parent->children->prev = node; + parent->children = node; + + next_symbol: + parent = node; + } + + sysprof_callgraph_populate_callers (self, parent, list_model_index); + + return parent; +} + +static void +reverse_symbols (SysprofSymbol **symbols, + guint n_symbols) +{ + guint half = n_symbols / 2; + + for (guint i = 0; i < half; i++) + { + SysprofSymbol *tmp = symbols[i]; + symbols[i] = symbols[n_symbols-1-i]; + symbols[n_symbols-1-i] = tmp; + } +} + +void +_sysprof_callgraph_categorize (SysprofCallgraph *self, + SysprofCallgraphNode *node) +{ + if (node->category) + return; + + if (node->parent && node->parent->category == 0) + _sysprof_callgraph_categorize (self, node->parent); + + switch (node->summary->symbol->kind) + { + case SYSPROF_SYMBOL_KIND_ROOT: + case SYSPROF_SYMBOL_KIND_THREAD: + case SYSPROF_SYMBOL_KIND_PROCESS: + node->category = SYSPROF_CALLGRAPH_CATEGORY_PRESENTATION; + break; + + case SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH: + node->category = SYSPROF_CALLGRAPH_CATEGORY_CONTEXT_SWITCH; + break; + + case SYSPROF_SYMBOL_KIND_KERNEL: + node->category = SYSPROF_CALLGRAPH_CATEGORY_KERNEL; + break; + + case SYSPROF_SYMBOL_KIND_UNWINDABLE: + node->category = SYSPROF_CALLGRAPH_CATEGORY_UNWINDABLE; + break; + + case SYSPROF_SYMBOL_KIND_USER: + node->category = _sysprof_callgraph_node_categorize (node); + + if (node->category > SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED) + break; + + G_GNUC_FALLTHROUGH; + + default: + { + SysprofCallgraphNode *parent = node->parent; + + while (parent != NULL) + { + if (parent->category & SYSPROF_CALLGRAPH_CATEGORY_INHERIT) + { + node->category = parent->category; + return; + } + + parent = parent->parent; + } + + node->category = SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED; + break; + } + } +} + +static void +sysprof_callgraph_add_traceable (SysprofCallgraph *self, + SysprofDocumentTraceable *traceable, + guint list_model_index) +{ + SysprofAddressContext final_context; + SysprofCallgraphNode *node; + SysprofSymbol **symbols; + guint stack_depth; + guint n_symbols; + int pid; + int tid; + + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + + pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable)); + tid = sysprof_document_traceable_get_thread_id (traceable); + stack_depth = sysprof_document_traceable_get_stack_depth (traceable); + + if (stack_depth == 0 || stack_depth > MAX_STACK_DEPTH) + return; + + symbols = g_newa (SysprofSymbol *, stack_depth + 4); + n_symbols = sysprof_document_symbolize_traceable (self->document, + traceable, + symbols, + stack_depth, + &final_context); + + g_assert (n_symbols <= stack_depth); + + /* Sometimes we get a very unhelpful unwind from the capture + * which is basically a single frame of "user space context". + * That means we got no amount of the stack, but we should + * really account costs to something in the application other + * than the [Application] entry itself so that it's more clear + * that it was a corrupted unwind when recording. + */ + if (n_symbols == 1 && + _sysprof_symbol_is_context_switch (symbols[0]) && + final_context == SYSPROF_ADDRESS_CONTEXT_USER) + symbols[0] = untraceable; + + /* We saved 3 extra spaces for these above so that we can + * tack on the "Process" symbol and the "All Processes" symbol. + * If the final address context places us in Kernel, we want + * to add a "- - Kernel - -" symbol to ensure that we are + * accounting cost to the kernel for the process. + */ + if (final_context == SYSPROF_ADDRESS_CONTEXT_KERNEL) + symbols[n_symbols++] = _sysprof_document_kernel_symbol (self->document); + + /* If the first thing we see is a context switch, then there is + * nothing after it to account for. Just skip the symbol as it + * provides nothing to us in the callgraph. + */ + if (_sysprof_symbol_is_context_switch (symbols[0])) + { + symbols++; + n_symbols--; + } + + if ((self->flags & SYSPROF_CALLGRAPH_FLAGS_BOTTOM_UP) != 0) + reverse_symbols (symbols, n_symbols); + + /* If the user requested thread-ids within each process, then + * insert a symbol for that before the real stacks. + */ + if ((self->flags & SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS) != 0) + symbols[n_symbols++] = _sysprof_document_thread_symbol (self->document, pid, tid); + + symbols[n_symbols++] = _sysprof_document_process_symbol (self->document, pid); + symbols[n_symbols++] = everything; + + node = sysprof_callgraph_add_trace (self, + symbols, + n_symbols, + list_model_index, + !!(self->flags & SYSPROF_CALLGRAPH_FLAGS_HIDE_SYSTEM_LIBRARIES)); + + node->is_toplevel = TRUE; + node->count++; + + if (node && self->augment_func) + self->augment_func (self, + node, + SYSPROF_DOCUMENT_FRAME (traceable), + TRUE, + self->augment_func_data); + + if ((self->flags & SYSPROF_CALLGRAPH_FLAGS_CATEGORIZE_FRAMES) != 0) + _sysprof_callgraph_categorize (self, node); +} + +static void +sysprof_callgraph_new_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofCallgraph *self = task_data; + guint n_items; + + g_assert (G_IS_TASK (task)); + g_assert (source_object == NULL); + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + n_items = g_list_model_get_n_items (self->traceables); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (self->traceables, i); + + sysprof_callgraph_add_traceable (self, traceable, i); + } + + g_task_return_pointer (task, g_object_ref (self), g_object_unref); +} + +void +_sysprof_callgraph_new_async (SysprofDocument *document, + SysprofCallgraphFlags flags, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(SysprofCallgraph) self = NULL; + g_autoptr(GTask) task = NULL; + GDestroyNotify summary_free; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (document)); + g_return_if_fail (G_IS_LIST_MODEL (traceables)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + if (augment_size > INLINE_AUGMENT_SIZE) + summary_free = (GDestroyNotify)sysprof_callgraph_summary_free_all; + else + summary_free = (GDestroyNotify)sysprof_callgraph_summary_free_self; + + self = g_object_new (SYSPROF_TYPE_CALLGRAPH, NULL); + self->flags = flags; + self->document = g_object_ref (document); + self->traceables = g_object_ref (traceables); + self->augment_size = augment_size; + self->augment_func = augment_func; + self->augment_func_data = augment_func_data; + self->augment_func_data_destroy = augment_func_data_destroy; + self->symbol_to_summary = g_hash_table_new_full ((GHashFunc)sysprof_symbol_hash, + (GEqualFunc)sysprof_symbol_equal, + NULL, + summary_free); + self->symbols = g_ptr_array_new (); + self->root.summary = sysprof_callgraph_get_summary (self, everything); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, _sysprof_callgraph_new_async); + g_task_set_task_data (task, g_object_ref (self), g_object_unref); + g_task_run_in_thread (task, sysprof_callgraph_new_worker); +} + +SysprofCallgraph * +_sysprof_callgraph_new_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +static inline gpointer +get_augmentation (SysprofCallgraph *self, + gpointer *augment_location) +{ + if (self->augment_size == 0) + return NULL; + + if (self->augment_size <= INLINE_AUGMENT_SIZE) + return augment_location; + + if (*augment_location == NULL) + *augment_location = g_malloc0 (self->augment_size); + + return *augment_location; +} + +gpointer +sysprof_callgraph_get_augment (SysprofCallgraph *self, + SysprofCallgraphNode *node) +{ + if (node == NULL) + node = &self->root; + + return get_augmentation (self, &node->augment[0]); +} + +gpointer +sysprof_callgraph_get_summary_augment (SysprofCallgraph *self, + SysprofCallgraphNode *node) +{ + if (node == NULL) + node = &self->root; + + return get_augmentation (self, &node->summary->augment[0]); +} + +gpointer +_sysprof_callgraph_get_symbol_augment (SysprofCallgraph *self, + SysprofSymbol *symbol) +{ + SysprofCallgraphSummary *summary; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (self), NULL); + g_return_val_if_fail (SYSPROF_IS_SYMBOL (symbol), NULL); + + if ((summary = g_hash_table_lookup (self->symbol_to_summary, symbol))) + return get_augmentation (self, &summary->augment[0]); + + return NULL; +} + +SysprofCallgraphNode * +sysprof_callgraph_node_parent (SysprofCallgraphNode *node) +{ + return node->parent; +} + +/** + * sysprof_callgraph_list_callers: + * @self: a #SysprofCallgraph + * @symbol: a #SysprofSymbol + * + * Gets a list of #SysprofSymbol that call @symbol. + * + * Returns: (trasfer full): a #GListModel of #SysprofCallgraphSymbol + */ +GListModel * +sysprof_callgraph_list_callers (SysprofCallgraph *self, + SysprofSymbol *symbol) +{ + SysprofCallgraphSummary *summary; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (self), NULL); + g_return_val_if_fail (SYSPROF_IS_SYMBOL (symbol), NULL); + + if ((summary = g_hash_table_lookup (self->symbol_to_summary, symbol))) + return _sysprof_callgraph_symbol_list_model_new (self, summary->callers); + + return G_LIST_MODEL (g_list_store_new (SYSPROF_TYPE_CALLGRAPH_SYMBOL)); +} + +/** + * sysprof_callgraph_list_traceables_for_symbol: + * @self: a #SysprofCallgraph + * @symbol: a #SysprofSymbol + * + * Gets a list of all the #SysprofTraceable within the callgraph + * that contain @symbol. + * + * Returns: (transfer full): a #GListModel of #SysprofTraceable + */ +GListModel * +sysprof_callgraph_list_traceables_for_symbol (SysprofCallgraph *self, + SysprofSymbol *symbol) +{ + SysprofCallgraphSummary *summary; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (self), NULL); + g_return_val_if_fail (SYSPROF_IS_SYMBOL (symbol), NULL); + + if ((summary = g_hash_table_lookup (self->symbol_to_summary, symbol))) + return _sysprof_document_bitset_index_new (self->traceables, summary->traceables); + + return G_LIST_MODEL (g_list_store_new (SYSPROF_TYPE_DOCUMENT_TRACEABLE)); +} + +GListModel * +sysprof_callgraph_list_traceables_for_symbols_matching (SysprofCallgraph *self, + const char *pattern) +{ + g_autoptr(GPatternSpec) pspec = NULL; + g_autoptr(EggBitset) bitset = NULL; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (self), NULL); + + if (pattern == NULL || pattern[0] == 0) + return g_object_ref (self->traceables); + + pspec = g_pattern_spec_new (pattern); + bitset = egg_bitset_new_empty (); + + for (guint i = 0; i < self->symbols->len; i++) + { + SysprofSymbol *symbol = g_ptr_array_index (self->symbols, i); + const char *name = sysprof_symbol_get_name (symbol); + + if (g_pattern_spec_match (pspec, strlen (name), name, NULL)) + { + SysprofCallgraphSummary *summary = g_hash_table_lookup (self->symbol_to_summary, symbol); + + if (summary != NULL) + egg_bitset_union (bitset, summary->traceables); + } + } + + return _sysprof_document_bitset_index_new (self->traceables, bitset); +} + +/** + * sysprof_callgraph_list_symbols: + * @self: a #SysprofCallgraph + * + * Gets a #GListModel of #SysprofCallgraphSymbol that an be used to + * display a function list and associated augmentation data. + * + * Returns: (transfer full): a #GListModel of #SysprofCallgraphSymbol + */ +GListModel * +sysprof_callgraph_list_symbols (SysprofCallgraph *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (self), NULL); + + return _sysprof_callgraph_symbol_list_model_new (self, self->symbols); +} + +static void +sysprof_callgraph_descendants_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofCallgraph *self = source_object; + SysprofSymbol *symbol = task_data; + + g_assert (G_IS_TASK (task)); + g_assert (SYSPROF_IS_CALLGRAPH (self)); + g_assert (SYSPROF_IS_SYMBOL (symbol)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + g_task_return_pointer (task, + _sysprof_descendants_model_new (self, symbol), + g_object_unref); +} + +void +sysprof_callgraph_descendants_async (SysprofCallgraph *self, + SysprofSymbol *symbol, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (SYSPROF_IS_CALLGRAPH (self)); + g_return_if_fail (SYSPROF_IS_SYMBOL (symbol)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_callgraph_descendants_async); + g_task_set_task_data (task, g_object_ref (symbol), g_object_unref); + g_task_run_in_thread (task, sysprof_callgraph_descendants_worker); +} + +GListModel * +sysprof_callgraph_descendants_finish (SysprofCallgraph *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/libsysprof/sysprof-callgraph.h b/src/libsysprof/sysprof-callgraph.h new file mode 100644 index 00000000..6704d8f0 --- /dev/null +++ b/src/libsysprof/sysprof-callgraph.h @@ -0,0 +1,137 @@ +/* sysprof-callgraph.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-callgraph-frame.h" +#include "sysprof-callgraph-symbol.h" +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CALLGRAPH (sysprof_callgraph_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofCallgraph, sysprof_callgraph, SYSPROF, CALLGRAPH, GObject) + +typedef struct _SysprofCallgraphNode SysprofCallgraphNode; + +/** + * SysprofAugmentationFunc: + * @callgraph: the callgraph being augmented + * @node: the node within the callgraph + * @frame: the frame used to generate this node + * @summarize: if summaries should be generated + * @user_data: closure data for augmentation func + * + * This function is called for the bottom most node in a trace as it is added + * to a callgraph. + * + * The augmentation func should augment the node in whatever way it sees fit + * and generally will want to walk up the node tree to the root to augment the + * parents as it goes. Your augmentation function is not called for each node, + * only the deepest node. + * + * If @summarize is %TRUE, then you should also generate summary augmentation + * using sysprof_callgraph_get_summary_augment() or similar. + */ +typedef void (*SysprofAugmentationFunc) (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gboolean summarize, + gpointer user_data); + +typedef enum _SysprofCallgraphCategory +{ + SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED = 1, + SYSPROF_CALLGRAPH_CATEGORY_PRESENTATION, + SYSPROF_CALLGRAPH_CATEGORY_A11Y, + SYSPROF_CALLGRAPH_CATEGORY_ACTIONS, + SYSPROF_CALLGRAPH_CATEGORY_CONTEXT_SWITCH, + SYSPROF_CALLGRAPH_CATEGORY_CSS, + SYSPROF_CALLGRAPH_CATEGORY_GRAPHICS, + SYSPROF_CALLGRAPH_CATEGORY_ICONS, + SYSPROF_CALLGRAPH_CATEGORY_INPUT, + SYSPROF_CALLGRAPH_CATEGORY_IO, + SYSPROF_CALLGRAPH_CATEGORY_IPC, + SYSPROF_CALLGRAPH_CATEGORY_JAVASCRIPT, + SYSPROF_CALLGRAPH_CATEGORY_KERNEL, + SYSPROF_CALLGRAPH_CATEGORY_LAYOUT, + SYSPROF_CALLGRAPH_CATEGORY_LOCKING, + SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP, + SYSPROF_CALLGRAPH_CATEGORY_MEMORY, + SYSPROF_CALLGRAPH_CATEGORY_PAINT, + SYSPROF_CALLGRAPH_CATEGORY_UNWINDABLE, + SYSPROF_CALLGRAPH_CATEGORY_WINDOWING, + + /* Not part of ABI */ + SYSPROF_CALLGRAPH_CATEGORY_LAST, +} SysprofCallgraphCategory; + +typedef enum _SysprofCallgraphFlags +{ + SYSPROF_CALLGRAPH_FLAGS_NONE = 0, + SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS = 1 << 1, + SYSPROF_CALLGRAPH_FLAGS_HIDE_SYSTEM_LIBRARIES = 1 << 2, + SYSPROF_CALLGRAPH_FLAGS_BOTTOM_UP = 1 << 3, + SYSPROF_CALLGRAPH_FLAGS_CATEGORIZE_FRAMES = 1 << 4, +} SysprofCallgraphFlags; + +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_list_symbols (SysprofCallgraph *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_list_callers (SysprofCallgraph *self, + SysprofSymbol *symbol); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_list_traceables_for_symbol (SysprofCallgraph *self, + SysprofSymbol *symbol); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_list_traceables_for_symbols_matching (SysprofCallgraph *self, + const char *pattern); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_callgraph_descendants_async (SysprofCallgraph *self, + SysprofSymbol *symbol, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_callgraph_descendants_finish (SysprofCallgraph *self, + GAsyncResult *result, + GError **error); +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_get_augment (SysprofCallgraph *self, + SysprofCallgraphNode *node); +SYSPROF_AVAILABLE_IN_ALL +gpointer sysprof_callgraph_get_summary_augment (SysprofCallgraph *self, + SysprofCallgraphNode *node); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraphNode *sysprof_callgraph_node_parent (SysprofCallgraphNode *node); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraph *sysprof_callgraph_frame_get_callgraph (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraphCategory sysprof_callgraph_frame_get_category (SysprofCallgraphFrame *self); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraph *sysprof_callgraph_symbol_get_callgraph (SysprofCallgraphSymbol *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-capture-autocleanups.h b/src/libsysprof/sysprof-capture-autocleanups.h deleted file mode 100644 index d640b7ee..00000000 --- a/src/libsysprof/sysprof-capture-autocleanups.h +++ /dev/null @@ -1,41 +0,0 @@ -/* sysprof-capture-gobject.h - * - * Copyright 2020 Endless Mobile, Inc. - * - * Author: - * - Philip Withnall - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-capture.h" - -G_BEGIN_DECLS - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureCondition, sysprof_capture_condition_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureCursor, sysprof_capture_cursor_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureReader, sysprof_capture_reader_unref) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) - -G_END_DECLS diff --git a/src/libsysprof/sysprof-capture-frame-object.c b/src/libsysprof/sysprof-capture-frame-object.c deleted file mode 100644 index 1cf6e983..00000000 --- a/src/libsysprof/sysprof-capture-frame-object.c +++ /dev/null @@ -1,72 +0,0 @@ -/* sysprof-capture-frame-object.c - * - * Copyright 2023 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-capture-frame-object-private.h" - -struct _SysprofCaptureFrameObject -{ - GObject parent_instance; - GMappedFile *mapped_file; - const SysprofCaptureFrame *frame; - guint is_native : 1; -}; - -G_DEFINE_FINAL_TYPE (SysprofCaptureFrameObject, sysprof_capture_frame_object, G_TYPE_OBJECT) - -static void -sysprof_capture_frame_object_finalize (GObject *object) -{ - SysprofCaptureFrameObject *self = (SysprofCaptureFrameObject *)object; - - g_clear_pointer (&self->mapped_file, g_mapped_file_unref); - self->frame = NULL; - - G_OBJECT_CLASS (sysprof_capture_frame_object_parent_class)->finalize (object); -} - -static void -sysprof_capture_frame_object_class_init (SysprofCaptureFrameObjectClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_capture_frame_object_finalize; -} - -static void -sysprof_capture_frame_object_init (SysprofCaptureFrameObject *self) -{ -} - -SysprofCaptureFrameObject * -sysprof_capture_frame_object_new (GMappedFile *mapped_file, - gconstpointer data, - gboolean is_native) -{ - SysprofCaptureFrameObject *self; - - self = g_object_new (SYSPROF_TYPE_CAPTURE_FRAME_OBJECT, NULL); - self->mapped_file = g_mapped_file_ref (mapped_file); - self->frame = data; - self->is_native = !!is_native; - - return self; -} diff --git a/src/libsysprof/sysprof-capture-gobject.c b/src/libsysprof/sysprof-capture-gobject.c deleted file mode 100644 index 4e6f660d..00000000 --- a/src/libsysprof/sysprof-capture-gobject.c +++ /dev/null @@ -1,92 +0,0 @@ -/* sysprof-capture-gobject.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include - -#include "sysprof-capture-gobject.h" - -G_DEFINE_BOXED_TYPE (SysprofCaptureReader, sysprof_capture_reader, (GBoxedCopyFunc)sysprof_capture_reader_ref, (GBoxedFreeFunc)sysprof_capture_reader_unref) -G_DEFINE_BOXED_TYPE (SysprofCaptureWriter, sysprof_capture_writer, (GBoxedCopyFunc)sysprof_capture_writer_ref, (GBoxedFreeFunc)sysprof_capture_writer_unref) -G_DEFINE_BOXED_TYPE (SysprofCaptureCursor, sysprof_capture_cursor, (GBoxedCopyFunc)sysprof_capture_cursor_ref, (GBoxedFreeFunc)sysprof_capture_cursor_unref) - -SysprofCaptureReader * -sysprof_capture_reader_new_with_error (const char *filename, - GError **error) -{ - SysprofCaptureReader *ret; - - if (!(ret = sysprof_capture_reader_new (filename))) - g_set_error_literal (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - g_strerror (errno)); - - return ret; -} - -SysprofCaptureReader * -sysprof_capture_reader_new_from_fd_with_error (int fd, - GError **error) -{ - SysprofCaptureReader *ret; - - if (!(ret = sysprof_capture_reader_new_from_fd (fd))) - g_set_error_literal (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - g_strerror (errno)); - - return ret; -} - -SysprofCaptureReader * -sysprof_capture_writer_create_reader_with_error (SysprofCaptureWriter *self, - GError **error) -{ - SysprofCaptureReader *ret; - - if (!(ret = sysprof_capture_writer_create_reader (self))) - g_set_error_literal (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - g_strerror (errno)); - - return ret; -} - -bool -sysprof_capture_reader_save_as_with_error (SysprofCaptureReader *self, - const char *filename, - GError **error) -{ - if (!sysprof_capture_reader_save_as (self, filename)) - { - g_set_error_literal (error, - G_FILE_ERROR, - g_file_error_from_errno (errno), - g_strerror (errno)); - return false; - } - - return true; -} diff --git a/src/libsysprof/sysprof-capture-gobject.h b/src/libsysprof/sysprof-capture-gobject.h deleted file mode 100644 index ca6fbe23..00000000 --- a/src/libsysprof/sysprof-capture-gobject.h +++ /dev/null @@ -1,58 +0,0 @@ -/* sysprof-capture-gobject.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CAPTURE_READER (sysprof_capture_reader_get_type()) -#define SYSPROF_TYPE_CAPTURE_WRITER (sysprof_capture_writer_get_type()) -#define SYSPROF_TYPE_CAPTURE_CURSOR (sysprof_capture_cursor_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -GType sysprof_capture_reader_get_type (void); -SYSPROF_AVAILABLE_IN_ALL -GType sysprof_capture_writer_get_type (void); -SYSPROF_AVAILABLE_IN_ALL -GType sysprof_capture_cursor_get_type (void); - -SYSPROF_AVAILABLE_IN_3_38 -SysprofCaptureReader *sysprof_capture_reader_new_with_error (const char *filename, - GError **error); -SYSPROF_AVAILABLE_IN_3_38 -SysprofCaptureReader *sysprof_capture_reader_new_from_fd_with_error (int fd, - GError **error); -SYSPROF_AVAILABLE_IN_3_38 -SysprofCaptureReader *sysprof_capture_writer_create_reader_with_error (SysprofCaptureWriter *self, - GError **error); -SYSPROF_AVAILABLE_IN_3_38 -bool sysprof_capture_reader_save_as_with_error (SysprofCaptureReader *self, - const char *filename, - GError **error); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-capture-model.c b/src/libsysprof/sysprof-capture-model.c deleted file mode 100644 index b9ae28f0..00000000 --- a/src/libsysprof/sysprof-capture-model.c +++ /dev/null @@ -1,165 +0,0 @@ -/* sysprof-capture-model.c - * - * Copyright 2023 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-capture-frame-object-private.h" -#include "sysprof-capture-model.h" - -struct _SysprofCaptureModel -{ - GObject parent_instance; - GPtrArray *frames; - GMappedFile *mapped_file; - SysprofCaptureFileHeader header; - guint is_native : 1; -}; - -static GType -sysprof_capture_model_get_item_type (GListModel *model) -{ - return SYSPROF_TYPE_CAPTURE_FRAME_OBJECT; -} - -static guint -sysprof_capture_model_get_n_items (GListModel *model) -{ - return SYSPROF_CAPTURE_MODEL (model)->frames->len; -} - -static gpointer -sysprof_capture_model_get_item (GListModel *model, - guint position) -{ - SysprofCaptureModel *self = SYSPROF_CAPTURE_MODEL (model); - - if (position >= self->frames->len) - return NULL; - - return sysprof_capture_frame_object_new (self->mapped_file, - g_ptr_array_index (self->frames, position), - self->is_native); -} - -static void -list_model_iface_init (GListModelInterface *iface) -{ - iface->get_item_type = sysprof_capture_model_get_item_type; - iface->get_n_items = sysprof_capture_model_get_n_items; - iface->get_item = sysprof_capture_model_get_item; -} - -G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofCaptureModel, sysprof_capture_model, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) - -static void -sysprof_capture_model_finalize (GObject *object) -{ - SysprofCaptureModel *self = (SysprofCaptureModel *)object; - - g_clear_pointer (&self->mapped_file, g_mapped_file_unref); - g_clear_pointer (&self->frames, g_ptr_array_unref); - - G_OBJECT_CLASS (sysprof_capture_model_parent_class)->finalize (object); -} -static void -sysprof_capture_model_class_init (SysprofCaptureModelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_capture_model_finalize; -} - -static void -sysprof_capture_model_init (SysprofCaptureModel *self) -{ - self->frames = g_ptr_array_new (); -} - -static gboolean -sysprof_capture_model_load (SysprofCaptureModel *self, - int capture_fd, - GError **error) -{ - const guint8 *data; - goffset pos; - gsize len; - - g_assert (SYSPROF_IS_CAPTURE_MODEL (self)); - g_assert (capture_fd > -1); - - if (!(self->mapped_file = g_mapped_file_new_from_fd (capture_fd, FALSE, error))) - return FALSE; - - data = (const guint8 *)g_mapped_file_get_contents (self->mapped_file); - len = g_mapped_file_get_length (self->mapped_file); - - if (len < sizeof self->header) - return FALSE; - - /* Keep a copy of our header */ - memcpy (&self->header, data, sizeof self->header); -#if G_BYTE_ORDER == G_LITTLE_ENDIAN - self->is_native = !!self->header.little_endian; -#else - self->is_native = !self->header.little_endian; -#endif - - if (!self->is_native) - { - self->header.time = GUINT64_SWAP_LE_BE (self->header.time); - self->header.end_time = GUINT64_SWAP_LE_BE (self->header.end_time); - } - - pos = sizeof self->header; - while (pos < (len - sizeof(guint16))) - { - guint16 frame_len; - - memcpy (&frame_len, &data[pos], sizeof frame_len); - if (!self->is_native) - frame_len = GUINT16_SWAP_LE_BE (self->is_native); - - if (frame_len < sizeof (SysprofCaptureFrame)) - break; - - g_ptr_array_add (self->frames, (gpointer)&data[pos]); - - pos += frame_len; - } - - return TRUE; -} - -SysprofCaptureModel * -sysprof_capture_model_new_from_fd (int capture_fd, - GError **error) -{ - g_autoptr(SysprofCaptureModel) self = NULL; - - g_return_val_if_fail (capture_fd > -1, NULL); - - self = g_object_new (SYSPROF_TYPE_CAPTURE_MODEL, NULL); - - if (!sysprof_capture_model_load (self, capture_fd, error)) - return NULL; - - return g_steal_pointer (&self); -} diff --git a/src/libsysprof/sysprof-capture-symbol-resolver.c b/src/libsysprof/sysprof-capture-symbol-resolver.c deleted file mode 100644 index 8986c6bd..00000000 --- a/src/libsysprof/sysprof-capture-symbol-resolver.c +++ /dev/null @@ -1,137 +0,0 @@ -/* sysprof-capture-symbol-resolver.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-capture-symbol-resolver" - -#include "config.h" - -#include - -#include "sysprof-capture-symbol-resolver.h" -#include "sysprof-platform.h" -#include "sysprof-private.h" -#include "sysprof-symbol-map.h" - -/** - * SECTION:sysprof-capture-symbol-resolver: - * @title: SysprofCaptureSymbolResolver - * @short_description: resolve symbols from embedded data within the capture - * - * This looks for an embedded file "__symbols__" in the capture and tries to - * decode them using the data stored within that file. - * - * This is useful when moving capture files between machines as it will allow - * the viewer machine to get access to the symbols without having to copy them - * to the local system. - * - * Since: 3.34 - */ - -struct _SysprofCaptureSymbolResolver -{ - GObject parent_instance; - SysprofSymbolMap *map; -}; - -static void symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofCaptureSymbolResolver, sysprof_capture_symbol_resolver, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SYMBOL_RESOLVER, symbol_resolver_iface_init)) - -static void -sysprof_capture_symbol_resolver_finalize (GObject *object) -{ - SysprofCaptureSymbolResolver *self = (SysprofCaptureSymbolResolver *)object; - - g_clear_pointer (&self->map, sysprof_symbol_map_free); - - G_OBJECT_CLASS (sysprof_capture_symbol_resolver_parent_class)->finalize (object); -} - -static void -sysprof_capture_symbol_resolver_class_init (SysprofCaptureSymbolResolverClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_capture_symbol_resolver_finalize; -} - -static void -sysprof_capture_symbol_resolver_init (SysprofCaptureSymbolResolver *self) -{ - self->map = sysprof_symbol_map_new (); -} - -SysprofSymbolResolver * -sysprof_capture_symbol_resolver_new (void) -{ - return g_object_new (SYSPROF_TYPE_CAPTURE_SYMBOL_RESOLVER, NULL); -} - -static void -sysprof_capture_symbol_resolver_load (SysprofSymbolResolver *resolver, - SysprofCaptureReader *reader) -{ - SysprofCaptureSymbolResolver *self = (SysprofCaptureSymbolResolver *)resolver; - gint byte_order; - gint fd; - - g_assert (SYSPROF_IS_CAPTURE_SYMBOL_RESOLVER (self)); - g_assert (reader != NULL); - - byte_order = sysprof_capture_reader_get_byte_order (reader); - - if (-1 == (fd = sysprof_memfd_create ("[symbol-decoder]"))) - return; - - if (sysprof_capture_reader_read_file_fd (reader, "__symbols__", fd)) - { - lseek (fd, 0, SEEK_SET); - sysprof_symbol_map_deserialize (self->map, byte_order, fd); - } - - close (fd); -} - -gchar * -sysprof_capture_symbol_resolver_resolve_with_context (SysprofSymbolResolver *resolver, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag) -{ - SysprofCaptureSymbolResolver *self = (SysprofCaptureSymbolResolver *)resolver; - const gchar *name; - - g_assert (SYSPROF_IS_CAPTURE_SYMBOL_RESOLVER (self)); - - if ((name = sysprof_symbol_map_lookup (self->map, time, pid, address, tag))) - return g_strdup (name); - - return NULL; -} - -static void -symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface) -{ - iface->load = sysprof_capture_symbol_resolver_load; - iface->resolve_with_context = sysprof_capture_symbol_resolver_resolve_with_context; -} diff --git a/src/libsysprof/sysprof-capture-symbol-resolver.h b/src/libsysprof/sysprof-capture-symbol-resolver.h deleted file mode 100644 index afb95084..00000000 --- a/src/libsysprof/sysprof-capture-symbol-resolver.h +++ /dev/null @@ -1,35 +0,0 @@ -/* sysprof-capture-symbol-resolver.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-symbol-resolver.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CAPTURE_SYMBOL_RESOLVER (sysprof_capture_symbol_resolver_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofCaptureSymbolResolver, sysprof_capture_symbol_resolver, SYSPROF, CAPTURE_SYMBOL_RESOLVER, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSymbolResolver *sysprof_capture_symbol_resolver_new (void); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-categories-private.h b/src/libsysprof/sysprof-categories-private.h new file mode 100644 index 00000000..ce46d9f4 --- /dev/null +++ b/src/libsysprof/sysprof-categories-private.h @@ -0,0 +1,36 @@ +/* sysprof-categories-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-callgraph.h" + +G_BEGIN_DECLS + +typedef struct _SysprofCategories SysprofCategories; + +SysprofCategories *sysprof_categories_get_default (void) G_GNUC_CONST; +SysprofCategories *sysprof_categories_new (void); +void sysprof_categories_free (SysprofCategories *categories); +SysprofCallgraphCategory sysprof_categories_lookup (SysprofCategories *categories, + const char *binary_nick, + const char *symbol); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-categories.c b/src/libsysprof/sysprof-categories.c new file mode 100644 index 00000000..310ac8ec --- /dev/null +++ b/src/libsysprof/sysprof-categories.c @@ -0,0 +1,296 @@ +/* sysprof-categories.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "libsysprof-resources.h" + +#include "sysprof-callgraph-private.h" +#include "sysprof-categories-private.h" +#include "sysprof-enums.h" + +#include "line-reader-private.h" + +struct _SysprofCategories +{ + GHashTable *binary_nick_to_rules; +}; + +enum { + MATCH_EXACT, + MATCH_PREFIX, + MATCH_SUFFIX, +}; + +typedef struct _Rule +{ + guint8 kind : 2; + guint8 inherit : 1; + guint8 category : 5; + char *match; +} Rule; + +static SysprofCallgraphCategory +parse_category (const char *category) +{ + static GEnumClass *enum_class; + const GEnumValue *value; + + if (enum_class == NULL) + enum_class = g_type_class_ref (SYSPROF_TYPE_CALLGRAPH_CATEGORY); + + if (!(value = g_enum_get_value_by_nick (enum_class, category))) + return 0; + + return value->value; +} + +static void +clear_rule (gpointer element) +{ + Rule *rule = element; + + g_clear_pointer (&rule->match, g_free); +} + +static void +sysprof_categories_add_rule (SysprofCategories *categories, + const char *binary_nick, + const char *match, + SysprofCallgraphCategory category, + gboolean inherit) +{ + Rule rule; + GArray *ar; + + g_assert (categories != NULL); + g_assert (binary_nick != NULL); + g_assert (match != NULL); + + if (match[0] == 0) + return; + + if G_UNLIKELY (!(ar = g_hash_table_lookup (categories->binary_nick_to_rules, binary_nick))) + { + ar = g_array_new (FALSE, FALSE, sizeof (Rule)); + g_array_set_clear_func (ar, clear_rule); + g_hash_table_insert (categories->binary_nick_to_rules, + g_strdup (binary_nick), + ar); + } + + if (match[0] == '*') + { + rule.kind = MATCH_SUFFIX; + rule.match = g_strdup (&match[1]); + rule.category = category; + rule.inherit = inherit; + + g_array_append_val (ar, rule); + } + else if (match[strlen(match)-1] == '*') + { + rule.kind = MATCH_PREFIX; + rule.match = g_strndup (match, strlen (match)-1); + rule.category = category; + rule.inherit = inherit; + + g_array_append_val (ar, rule); + } + else + { + rule.kind = MATCH_EXACT; + rule.match = g_strdup (match); + rule.category = category; + rule.inherit = inherit; + + g_array_append_val (ar, rule); + } +} + +SysprofCategories * +sysprof_categories_new (void) +{ + SysprofCategories *categories; + g_autoptr(GBytes) bytes = NULL; + g_autofree char *binary_nick = NULL; + const char *str; + const char *lineptr; + gsize line_len; + gsize len; + guint lineno = 0; + LineReader reader; + + g_resources_register (libsysprof_get_resource ()); + + if (!(bytes = g_resources_lookup_data ("/org/gnome/libsysprof/categories.txt", 0, NULL))) + return NULL; + + if (!(str = (const char *)g_bytes_get_data (bytes, &len))) + return NULL; + + line_reader_init (&reader, (char *)str, len); + + categories = g_new0 (SysprofCategories, 1); + categories->binary_nick_to_rules = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify)g_array_unref); + + while ((lineptr = line_reader_next (&reader, &line_len))) + { + g_autofree char *line = g_strndup (lineptr, line_len); + SysprofCallgraphCategory category_value; + g_auto(GStrv) parts = NULL; + const char *match = NULL; + const char *category = NULL; + const char *inherit = NULL; + + lineno++; + g_strstrip (line); + + /* # starts comment line */ + if (line[0] == 0 || line[0] == '#') + continue; + + /* Group lines look like "binary nick:\n" */ + if (g_str_has_suffix (line, ":")) + { + line[line_len-1] = 0; + g_set_str (&binary_nick, g_strstrip (line)); + continue; + } + + parts = g_strsplit (line, " ", 0); + + for (guint i = 0; parts[i]; i++) + { + g_strstrip (parts[i]); + + if (parts[i][0] == 0) + continue; + + if (match == NULL) + { + match = parts[i]; + continue; + } + + if (category == NULL) + { + category = parts[i]; + continue; + } + + if (inherit == NULL) + { + inherit = parts[i]; + continue; + } + + break; + } + + if (match == NULL || category == NULL) + { + g_warning ("categories.txt: line: %d: Incomplete rule", lineno); + continue; + } + + if (inherit && !g_str_equal (inherit, "inherit")) + { + g_warning ("categories.txt line %d: malformated inherit", lineno); + continue; + } + + if (!(category_value = parse_category (category))) + { + g_warning ("categories.txt line %d: malformated category", lineno); + continue; + } + + sysprof_categories_add_rule (categories, binary_nick, match, category_value, !!inherit); + } + + return categories; +} + +void +sysprof_categories_free (SysprofCategories *categories) +{ + if (categories != NULL) + { + g_clear_pointer (&categories->binary_nick_to_rules, g_hash_table_unref); + g_free (categories); + } +} + +SysprofCallgraphCategory +sysprof_categories_lookup (SysprofCategories *categories, + const char *binary_nick, + const char *symbol) +{ + GArray *rules; + + if (categories == NULL) + categories = sysprof_categories_get_default (); + + if (binary_nick == NULL || symbol == NULL) + return 0; + + if (!(rules = g_hash_table_lookup (categories->binary_nick_to_rules, binary_nick))) + return 0; + + for (guint i = 0; i < rules->len; i++) + { + const Rule *rule = &g_array_index (rules, Rule, i); + gboolean ret; + + if (rule->kind == MATCH_EXACT) + ret = strcmp (rule->match, symbol) == 0; + else if (rule->kind == MATCH_PREFIX) + ret = g_str_has_prefix (symbol, rule->match); + else if (rule->kind == MATCH_SUFFIX) + ret = g_str_has_suffix (symbol, rule->match); + else + ret = FALSE; + + if (ret) + { + if (rule->inherit) + return rule->category | SYSPROF_CALLGRAPH_CATEGORY_INHERIT; + + return rule->category; + } + } + + return 0; +} + +SysprofCategories * +sysprof_categories_get_default (void) +{ + static SysprofCategories *instance; + + if (instance == NULL) + instance = sysprof_categories_new (); + + return instance; +} diff --git a/src/libsysprof/sysprof-symbol-resolver-private.h b/src/libsysprof/sysprof-category-summary-private.h similarity index 72% rename from src/libsysprof/sysprof-symbol-resolver-private.h rename to src/libsysprof/sysprof-category-summary-private.h index 76cbe45e..5516bcc0 100644 --- a/src/libsysprof/sysprof-symbol-resolver-private.h +++ b/src/libsysprof/sysprof-category-summary-private.h @@ -1,6 +1,6 @@ -/* sysprof-symbol-resolver-private.h +/* sysprof-category-summary-private.h * - * Copyright 2021 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,16 @@ #pragma once -#include -#include +#include "sysprof-category-summary.h" G_BEGIN_DECLS -char *_sysprof_symbol_resolver_load_file (SysprofCaptureReader *reader, - const char *path); +struct _SysprofCategorySummary +{ + GObject parent_instance; + SysprofCallgraphCategory category; + guint64 count; + guint64 total; +}; G_END_DECLS diff --git a/src/libsysprof/sysprof-category-summary.c b/src/libsysprof/sysprof-category-summary.c new file mode 100644 index 00000000..08402db9 --- /dev/null +++ b/src/libsysprof/sysprof-category-summary.c @@ -0,0 +1,199 @@ +/* sysprof-category-summary.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-category-summary-private.h" +#include "sysprof-enums.h" + +enum { + PROP_0, + PROP_CATEGORY, + PROP_CATEGORY_NAME, + PROP_FRACTION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofCategorySummary, sysprof_category_summary, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_category_summary_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofCategorySummary *self = SYSPROF_CATEGORY_SUMMARY (object); + + switch (prop_id) + { + case PROP_CATEGORY: + g_value_set_enum (value, sysprof_category_summary_get_category (self)); + break; + + case PROP_CATEGORY_NAME: + g_value_set_string (value, sysprof_category_summary_get_category_name (self)); + break; + + case PROP_FRACTION: + g_value_set_double (value, sysprof_category_summary_get_fraction (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_category_summary_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofCategorySummary *self = SYSPROF_CATEGORY_SUMMARY (object); + + switch (prop_id) + { + case PROP_CATEGORY: + self->category = g_value_get_enum (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_category_summary_class_init (SysprofCategorySummaryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_category_summary_get_property; + object_class->set_property = sysprof_category_summary_set_property; + + properties[PROP_CATEGORY] = + g_param_spec_enum ("category", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH_CATEGORY, + SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_CATEGORY_NAME] = + g_param_spec_string ("category-name", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_FRACTION] = + g_param_spec_double ("fraction", NULL, NULL, + 0, 1, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_category_summary_init (SysprofCategorySummary *self) +{ +} + +SysprofCallgraphCategory +sysprof_category_summary_get_category (SysprofCategorySummary *self) +{ + return self->category; +} + +double +sysprof_category_summary_get_fraction (SysprofCategorySummary *self) +{ + return CLAMP (self->count / (double)self->total, .0, 1.); +} + +const char * +sysprof_category_summary_get_category_name (SysprofCategorySummary *self) +{ + g_return_val_if_fail (SYSPROF_IS_CATEGORY_SUMMARY (self), NULL); + + switch (self->category) + { + case SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED: + return _("Uncategorized"); + + case SYSPROF_CALLGRAPH_CATEGORY_A11Y: + return _("Accessibility"); + + case SYSPROF_CALLGRAPH_CATEGORY_ACTIONS: + return _("Actions"); + + case SYSPROF_CALLGRAPH_CATEGORY_CONTEXT_SWITCH: + return _("Context Switches"); + + case SYSPROF_CALLGRAPH_CATEGORY_CSS: + return _("CSS"); + + case SYSPROF_CALLGRAPH_CATEGORY_GRAPHICS: + return _("Graphics"); + + case SYSPROF_CALLGRAPH_CATEGORY_ICONS: + return _("Icons"); + + case SYSPROF_CALLGRAPH_CATEGORY_INPUT: + return _("Input"); + + case SYSPROF_CALLGRAPH_CATEGORY_IO: + return _("IO"); + + case SYSPROF_CALLGRAPH_CATEGORY_IPC: + return _("IPC"); + + case SYSPROF_CALLGRAPH_CATEGORY_JAVASCRIPT: + return _("JavaScript"); + + case SYSPROF_CALLGRAPH_CATEGORY_KERNEL: + return _("Kernel"); + + case SYSPROF_CALLGRAPH_CATEGORY_LAYOUT: + return _("Layout"); + + case SYSPROF_CALLGRAPH_CATEGORY_LOCKING: + return _("Locking"); + + case SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP: + return _("Main Loop"); + + case SYSPROF_CALLGRAPH_CATEGORY_MEMORY: + return _("Memory"); + + case SYSPROF_CALLGRAPH_CATEGORY_PAINT: + return _("Paint"); + + case SYSPROF_CALLGRAPH_CATEGORY_UNWINDABLE: + return _("Unwindable"); + + case SYSPROF_CALLGRAPH_CATEGORY_WINDOWING: + return _("Windowing"); + + case SYSPROF_CALLGRAPH_CATEGORY_PRESENTATION: + case SYSPROF_CALLGRAPH_CATEGORY_LAST: + default: + return NULL; + } +} diff --git a/src/libsysprof/sysprof-governor-source.h b/src/libsysprof/sysprof-category-summary.h similarity index 56% rename from src/libsysprof/sysprof-governor-source.h rename to src/libsysprof/sysprof-category-summary.h index 77968883..88d9cca8 100644 --- a/src/libsysprof/sysprof-governor-source.h +++ b/src/libsysprof/sysprof-category-summary.h @@ -1,6 +1,6 @@ -/* sysprof-governor-source.h +/* sysprof-category-summary.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,21 +20,20 @@ #pragma once -#include "sysprof-source.h" +#include "sysprof-callgraph.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_GOVERNOR_SOURCE (sysprof_governor_source_get_type()) +#define SYSPROF_TYPE_CATEGORY_SUMMARY (sysprof_category_summary_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofGovernorSource, sysprof_governor_source, SYSPROF, GOVERNOR_SOURCE, GObject) +G_DECLARE_FINAL_TYPE (SysprofCategorySummary, sysprof_category_summary, SYSPROF, CATEGORY_SUMMARY, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_governor_source_new (void); +SysprofCallgraphCategory sysprof_category_summary_get_category (SysprofCategorySummary *self); SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_governor_source_get_disable_governor (SysprofGovernorSource *self); +const char *sysprof_category_summary_get_category_name (SysprofCategorySummary *self); SYSPROF_AVAILABLE_IN_ALL -void sysprof_governor_source_set_disable_governor (SysprofGovernorSource *self, - gboolean disable_governor); +double sysprof_category_summary_get_fraction (SysprofCategorySummary *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-control-source.c b/src/libsysprof/sysprof-control-source.c deleted file mode 100644 index e39ebde8..00000000 --- a/src/libsysprof/sysprof-control-source.c +++ /dev/null @@ -1,313 +0,0 @@ -/* sysprof-control-source.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-control-source" - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mapped-ring-buffer.h" -#include "mapped-ring-buffer-source.h" - -#include "sysprof-control-source.h" - -#define CREATRING "CreatRing\0" -#define CREATRING_LEN 10 - -struct _SysprofControlSource -{ - GObject parent_instance; - SysprofCaptureWriter *writer; - GArray *source_ids; - -#ifdef G_OS_UNIX - GUnixConnection *conn; -#endif - - GCancellable *cancellable; - - /* Control messages are 10 bytes */ - gchar read_buf[10]; - - guint stopped : 1; - -}; - -typedef struct -{ - SysprofControlSource *self; - guint id; -} RingData; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofControlSource, sysprof_control_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -ring_data_free (RingData *rd) -{ - g_clear_object (&rd->self); - g_slice_free (RingData, rd); -} - -SysprofControlSource * -sysprof_control_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_CONTROL_SOURCE, NULL); -} - -static void -remove_source_id (gpointer data) -{ - guint *id = data; - g_source_remove (*id); -} - -static void -sysprof_control_source_finalize (GObject *object) -{ - SysprofControlSource *self = (SysprofControlSource *)object; - -#ifdef G_OS_UNIX - g_clear_object (&self->conn); -#endif - - if (self->source_ids->len > 0) - g_array_remove_range (self->source_ids, 0, self->source_ids->len); - - g_clear_pointer (&self->source_ids, g_array_unref); - - G_OBJECT_CLASS (sysprof_control_source_parent_class)->finalize (object); -} - -static void -sysprof_control_source_class_init (SysprofControlSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_control_source_finalize; -} - -static void -sysprof_control_source_init (SysprofControlSource *self) -{ - self->cancellable = g_cancellable_new (); - self->source_ids = g_array_new (FALSE, FALSE, sizeof (guint)); - g_array_set_clear_func (self->source_ids, remove_source_id); -} - -static bool -event_frame_cb (const void *data, - size_t *length, - void *user_data) -{ - const SysprofCaptureFrame *fr = data; - RingData *rd = user_data; - - g_assert (rd != NULL); - g_assert (SYSPROF_IS_CONTROL_SOURCE (rd->self)); - g_assert (rd->id > 0); - - if G_UNLIKELY (rd->self->writer == NULL || - *length < sizeof *fr || - *length < fr->len || - fr->type >= SYSPROF_CAPTURE_FRAME_LAST) - goto remove_source; - - _sysprof_capture_writer_add_raw (rd->self->writer, fr); - - *length = fr->len; - - return G_SOURCE_CONTINUE; - -remove_source: - for (guint i = 0; i < rd->self->source_ids->len; i++) - { - guint id = g_array_index (rd->self->source_ids, guint, i); - - if (id == rd->id) - { - g_array_remove_index (rd->self->source_ids, i); - break; - } - } - - return G_SOURCE_REMOVE; -} - -#ifdef G_OS_UNIX -static void -sysprof_control_source_read_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(SysprofControlSource) self = user_data; - GInputStream *input_stream = (GInputStream *)object; - gssize ret; - - g_assert (SYSPROF_IS_CONTROL_SOURCE (self)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_INPUT_STREAM (input_stream)); - - ret = g_input_stream_read_finish (G_INPUT_STREAM (input_stream), result, NULL); - - if (ret == sizeof self->read_buf) - { - if (memcmp (self->read_buf, CREATRING, CREATRING_LEN) == 0) - { - g_autoptr(MappedRingBuffer) buffer = NULL; - - if ((buffer = mapped_ring_buffer_new_reader (0))) - { - int fd = mapped_ring_buffer_get_fd (buffer); - RingData *rd; - - rd = g_slice_new0 (RingData); - rd->self = g_object_ref (self); - rd->id = mapped_ring_buffer_create_source_full (buffer, - event_frame_cb, - rd, - (GDestroyNotify)ring_data_free); - - g_array_append_val (self->source_ids, rd->id); - g_unix_connection_send_fd (self->conn, fd, NULL, NULL); - } - } - - if (!g_cancellable_is_cancelled (self->cancellable)) - g_input_stream_read_async (G_INPUT_STREAM (input_stream), - self->read_buf, - sizeof self->read_buf, - G_PRIORITY_HIGH, - self->cancellable, - sysprof_control_source_read_cb, - g_object_ref (self)); - } -} -#endif - -static void -sysprof_control_source_modify_spawn (SysprofSource *source, - SysprofSpawnable *spawnable) -{ -#ifdef G_OS_UNIX - SysprofControlSource *self = (SysprofControlSource *)source; - g_autofree gchar *child_no_str = NULL; - g_autoptr(GSocketConnection) stream = NULL; - g_autoptr(GSocket) sock = NULL; - GInputStream *input_stream; - int fds[2]; - int child_no; - - g_assert (SYSPROF_IS_SOURCE (source)); - g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); - - /* Create a socket pair to communicate D-Bus protocol over */ - if (socketpair (AF_LOCAL, SOCK_STREAM, 0, fds) != 0) - return; - - g_unix_set_fd_nonblocking (fds[0], TRUE, NULL); - g_unix_set_fd_nonblocking (fds[1], TRUE, NULL); - - /* @child_no is assigned the FD the child will receive. We can - * use that to set the environment vaiable of the control FD. - */ - child_no = sysprof_spawnable_take_fd (spawnable, fds[1], -1); - child_no_str = g_strdup_printf ("%d", child_no); - sysprof_spawnable_setenv (spawnable, "SYSPROF_CONTROL_FD", child_no_str); - - /* We need an IOStream for GDBusConnection to use. Since we need - * the ability to pass FDs, it must be a GUnixSocketConnection. - */ - if (!(sock = g_socket_new_from_fd (fds[0], NULL))) - { - close (fds[0]); - g_critical ("Failed to create GSocket"); - return; - } - - g_socket_set_blocking (sock, FALSE); - - stream = g_socket_connection_factory_create_connection (sock); - - g_assert (G_IS_UNIX_CONNECTION (stream)); - - self->conn = g_object_ref (G_UNIX_CONNECTION (stream)); - - input_stream = g_io_stream_get_input_stream (G_IO_STREAM (stream)); - - g_input_stream_read_async (input_stream, - self->read_buf, - sizeof self->read_buf, - G_PRIORITY_HIGH, - self->cancellable, - sysprof_control_source_read_cb, - g_object_ref (self)); -#endif -} - -static void -sysprof_control_source_stop (SysprofSource *source) -{ - SysprofControlSource *self = (SysprofControlSource *)source; - - g_assert (SYSPROF_IS_CONTROL_SOURCE (self)); - - self->stopped = TRUE; - - g_cancellable_cancel (self->cancellable); - - if (self->source_ids->len > 0) - g_array_remove_range (self->source_ids, 0, self->source_ids->len); - - sysprof_source_emit_finished (source); -} - -static void -sysprof_control_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofControlSource *self = (SysprofControlSource *)source; - - g_assert (SYSPROF_IS_CONTROL_SOURCE (self)); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - - if (writer != NULL) - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->set_writer = sysprof_control_source_set_writer; - iface->modify_spawn = sysprof_control_source_modify_spawn; - iface->stop = sysprof_control_source_stop; -} diff --git a/src/libsysprof/sysprof-control-source.h b/src/libsysprof/sysprof-control-source.h deleted file mode 100644 index b2d43467..00000000 --- a/src/libsysprof/sysprof-control-source.h +++ /dev/null @@ -1,35 +0,0 @@ -/* sysprof-control-source.h - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-source.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CONTROL_SOURCE (sysprof_control_source_get_type()) - -SYSPROF_AVAILABLE_IN_3_38 -G_DECLARE_FINAL_TYPE (SysprofControlSource, sysprof_control_source, SYSPROF, CONTROL_SOURCE, GObject) - -SYSPROF_AVAILABLE_IN_3_38 -SysprofControlSource *sysprof_control_source_new (void); - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-memprof-aid.h b/src/libsysprof/sysprof-controlfd-instrument-private.h similarity index 64% rename from src/libsysprof-ui/sysprof-memprof-aid.h rename to src/libsysprof/sysprof-controlfd-instrument-private.h index b5294d57..23352b3c 100644 --- a/src/libsysprof-ui/sysprof-memprof-aid.h +++ b/src/libsysprof/sysprof-controlfd-instrument-private.h @@ -1,6 +1,6 @@ -/* sysprof-memprof-aid.h +/* sysprof-controlfd-instrument-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,14 @@ #pragma once -#include "sysprof-aid.h" +#include "sysprof-instrument-private.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_MEMPROF_AID (sysprof_memprof_aid_get_type()) +#define SYSPROF_TYPE_CONTROLFD_INSTRUMENT (sysprof_controlfd_instrument_get_type()) -G_DECLARE_FINAL_TYPE (SysprofMemprofAid, sysprof_memprof_aid, SYSPROF, MEMPROF_AID, SysprofAid) +G_DECLARE_FINAL_TYPE (SysprofControlfdInstrument, sysprof_controlfd_instrument, SYSPROF, CONTROLFD_INSTRUMENT, SysprofInstrument) -SysprofAid *sysprof_memprof_aid_new (void); +SysprofInstrument *_sysprof_controlfd_instrument_new (void); G_END_DECLS diff --git a/src/libsysprof/sysprof-controlfd-instrument.c b/src/libsysprof/sysprof-controlfd-instrument.c new file mode 100644 index 00000000..6ee569cc --- /dev/null +++ b/src/libsysprof/sysprof-controlfd-instrument.c @@ -0,0 +1,352 @@ +/* sysprof-controlfd-instrument.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#ifdef G_OS_UNIX +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include "sysprof-controlfd-instrument-private.h" +#include "sysprof-recording-private.h" + +#ifdef G_OS_UNIX +# include "mapped-ring-buffer.h" +# include "mapped-ring-buffer-source-private.h" +#endif + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureReader, sysprof_capture_reader_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) + +struct _SysprofControlfdInstrument +{ + SysprofInstrument parent_instance; + +#ifdef G_OS_UNIX + GUnixConnection *connection; + char read_buf[10]; +#endif +}; + +G_DEFINE_FINAL_TYPE (SysprofControlfdInstrument, sysprof_controlfd_instrument, SYSPROF_TYPE_INSTRUMENT) + +#ifdef G_OS_UNIX +typedef struct _RingData +{ + SysprofCaptureWriter *writer; + GArray *source_ids; + guint id; +} RingData; + +static void +ring_data_free (gpointer data) +{ + RingData *ring_data = data; + + for (guint i = 0; i < ring_data->source_ids->len; i++) + { + guint *id = &g_array_index (ring_data->source_ids, guint, i); + + if (*id == ring_data->id) + { + *id = 0; + g_array_remove_index_fast (ring_data->source_ids, i); + break; + } + } + + ring_data->id = 0; + + g_clear_pointer (&ring_data->writer, sysprof_capture_writer_unref); + g_array_unref (ring_data->source_ids); + g_free (ring_data); +} + +static DexFuture * +sysprof_controlfd_instrument_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofControlfdInstrument *self = (SysprofControlfdInstrument *)instrument; + SysprofSpawnable *spawnable; + g_autofree char *child_no_str = NULL; + g_autoptr(GSocketConnection) stream = NULL; + g_autoptr(GSocket) sock = NULL; + int fds[2] = {-1, -1}; + int child_no; + + g_assert (SYSPROF_IS_CONTROLFD_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + /* If the recording is not spawning a process, then there is + * nothing for us to do. + */ + if (!(spawnable = _sysprof_recording_get_spawnable (recording))) + goto finish; + + /* Create a socket pair to send control messages over */ + if (socketpair (AF_LOCAL, SOCK_STREAM, 0, fds) != 0) + return dex_future_new_reject (G_IO_ERROR, + G_IO_ERROR_NOT_CONNECTED, + "Failed to create socketpair"); + + /* Set FDs non-blocking so that we can use them from main + * context iteration without blocking. + */ + g_unix_set_fd_nonblocking (fds[0], TRUE, NULL); + g_unix_set_fd_nonblocking (fds[1], TRUE, NULL); + + /* @child_no is assigned the FD the child will receive. We can + * use that to set the environment variable of the control FD. + */ + child_no = sysprof_spawnable_take_fd (spawnable, fds[1], -1); + child_no_str = g_strdup_printf ("%d", child_no); + sysprof_spawnable_setenv (spawnable, "SYSPROF_CONTROL_FD", child_no_str); + + if (!(sock = g_socket_new_from_fd (fds[0], NULL))) + { + close (fds[0]); + return dex_future_new_reject (G_IO_ERROR, + G_IO_ERROR_NOT_CONNECTED, + "Failed to create socket from FD"); + } + + self->connection = G_UNIX_CONNECTION (g_socket_connection_factory_create_connection (sock)); + +finish: + return dex_future_new_for_boolean (TRUE); +} + +typedef struct _SysprofControlfdRecording +{ + GIOStream *stream; + SysprofRecording *recording; + DexFuture *cancellable; + GArray *source_ids; +} SysprofControlfdRecording; + +static void +sysprof_controlfd_recording_free (gpointer data) +{ + SysprofControlfdRecording *state = data; + + dex_clear (&state->cancellable); + g_clear_pointer (&state->source_ids, g_array_unref); + g_clear_object (&state->recording); + g_clear_object (&state->stream); + g_free (state); +} + +static bool +sysprof_controlfd_instrument_frame_cb (gconstpointer data, + gsize *length, + gpointer user_data) +{ + const SysprofCaptureFrame *fr = data; + RingData *ring_data = user_data; + + g_assert (ring_data != NULL); + g_assert (ring_data->source_ids != NULL); + g_assert (ring_data->writer != NULL); + g_assert (ring_data->id > 0); + + if G_UNLIKELY (*length < sizeof *fr || + *length < fr->len || + fr->type >= SYSPROF_CAPTURE_FRAME_LAST) + return G_SOURCE_REMOVE; + + _sysprof_capture_writer_add_raw (ring_data->writer, fr); + + *length = fr->len; + + return G_SOURCE_CONTINUE; +} + +static DexFuture * +sysprof_controlfd_instrument_record_fiber (gpointer user_data) +{ + SysprofControlfdRecording *state = user_data; + g_autoptr(SysprofCaptureWriter) temp_writer = NULL; + SysprofCaptureWriter *writer; + g_autoptr(GError) error = NULL; + g_autofd int mem_fd = -1; + GInputStream *input; + + g_assert (state != NULL); + g_assert (SYSPROF_IS_RECORDING (state->recording)); + g_assert (DEX_IS_CANCELLABLE (state->cancellable)); + g_assert (state->source_ids != NULL); + + input = g_io_stream_get_input_stream (G_IO_STREAM (state->stream)); + + if (!(mem_fd = sysprof_memfd_create ("[controlfd-memfd]"))) + return dex_future_new_for_errno (errno); + + temp_writer = sysprof_capture_writer_new_from_fd (g_steal_fd (&mem_fd), 0); + writer = _sysprof_recording_writer (state->recording); + + for (;;) + { + g_autoptr(DexFuture) future = dex_input_stream_read_bytes (input, 10, 0); + g_autoptr(MappedRingBuffer) ring_buffer = NULL; + g_autoptr(GBytes) bytes = NULL; + const guint8 *data; + gsize len; + + dex_await (dex_future_any (dex_ref (future), + dex_ref (state->cancellable), + NULL), + &error); + + if (error != NULL) + goto handle_error; + + if (!(bytes = dex_await_boxed (dex_ref (future), &error))) + goto handle_error; + + data = g_bytes_get_data (bytes, &len); + if (len != 10 || memcmp (data, "CreatRing\0", 10) != 0) + break; + + if ((ring_buffer = mapped_ring_buffer_new_reader (0))) + { + int fd = mapped_ring_buffer_get_fd (ring_buffer); + RingData *ring_data; + + ring_data = g_new0 (RingData, 1); + ring_data->writer = sysprof_capture_writer_ref (temp_writer); + ring_data->source_ids = g_array_ref (state->source_ids); + ring_data->id = mapped_ring_buffer_create_source_full (ring_buffer, + sysprof_controlfd_instrument_frame_cb, + ring_data, + (GDestroyNotify)ring_data_free); + + g_array_append_val (state->source_ids, ring_data->id); + + g_unix_connection_send_fd (G_UNIX_CONNECTION (state->stream), fd, NULL, NULL); + } + } + +handle_error: + while (state->source_ids->len > 0) + { + guint id = g_array_index (state->source_ids, guint, state->source_ids->len-1); + state->source_ids->len--; + g_source_remove (id); + } + + if (temp_writer != NULL) + { + g_autoptr(SysprofCaptureReader) reader = sysprof_capture_writer_create_reader (temp_writer); + + if (reader != NULL) + sysprof_capture_writer_cat (writer, reader); + } + + if (error != NULL) + return dex_future_new_for_error (g_steal_pointer (&error)); + + return dex_future_new_for_boolean (TRUE); +} + +static void +_g_clear_source (gpointer data) +{ + guint *id = data; + + if (*id != 0) + g_source_remove (*id); +} + +static DexFuture * +sysprof_controlfd_instrument_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + SysprofControlfdInstrument *self = (SysprofControlfdInstrument *)instrument; + SysprofControlfdRecording *state; + SysprofSpawnable *spawnable; + + g_assert (SYSPROF_IS_CONTROLFD_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if (!(spawnable = _sysprof_recording_get_spawnable (recording))) + return dex_future_new_for_boolean (TRUE); + + state = g_new0 (SysprofControlfdRecording, 1); + state->recording = g_object_ref (recording); + state->stream = g_object_ref (G_IO_STREAM (self->connection)); + state->cancellable = dex_cancellable_new_from_cancellable (cancellable); + state->source_ids = g_array_new (FALSE, FALSE, sizeof (guint)); + g_array_set_clear_func (state->source_ids, _g_clear_source); + + return dex_scheduler_spawn (NULL, 0, + sysprof_controlfd_instrument_record_fiber, + state, + sysprof_controlfd_recording_free); +} + +static void +sysprof_controlfd_instrument_finalize (GObject *object) +{ + SysprofControlfdInstrument *self = (SysprofControlfdInstrument *)object; + + g_clear_object (&self->connection); + + G_OBJECT_CLASS (sysprof_controlfd_instrument_parent_class)->finalize (object); +} +#endif + +static void +sysprof_controlfd_instrument_class_init (SysprofControlfdInstrumentClass *klass) +{ +#ifdef G_OS_UNIX + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->finalize = sysprof_controlfd_instrument_finalize; + + instrument_class->prepare = sysprof_controlfd_instrument_prepare; + instrument_class->record = sysprof_controlfd_instrument_record; +#endif +} + +static void +sysprof_controlfd_instrument_init (SysprofControlfdInstrument *self) +{ +} + +SysprofInstrument * +_sysprof_controlfd_instrument_new (void) +{ +#ifndef G_OS_UNIX + g_warning_once ("SysprofControlfdInstrument not supported on this platform"); +#endif + + return g_object_new (SYSPROF_TYPE_CONTROLFD_INSTRUMENT, NULL); +} diff --git a/src/libsysprof/sysprof-capture-frame-object-private.h b/src/libsysprof/sysprof-cpu-info-private.h similarity index 70% rename from src/libsysprof/sysprof-capture-frame-object-private.h rename to src/libsysprof/sysprof-cpu-info-private.h index 34d6ce2e..1345231e 100644 --- a/src/libsysprof/sysprof-capture-frame-object-private.h +++ b/src/libsysprof/sysprof-cpu-info-private.h @@ -1,4 +1,4 @@ -/* sysprof-capture-frame-object-private.h +/* sysprof-cpu-info-private.h * * Copyright 2023 Christian Hergert * @@ -18,12 +18,15 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#include "sysprof-capture-frame-object.h" +#pragma once + +#include "sysprof-cpu-info.h" G_BEGIN_DECLS -SysprofCaptureFrameObject *sysprof_capture_frame_object_new (GMappedFile *mapped, - gconstpointer data, - gboolean is_native); +void _sysprof_cpu_info_set_core_id (SysprofCpuInfo *self, + guint core_id); +void _sysprof_cpu_info_set_model_name (SysprofCpuInfo *self, + const char *model_name); G_END_DECLS diff --git a/src/libsysprof/sysprof-cpu-info.c b/src/libsysprof/sysprof-cpu-info.c new file mode 100644 index 00000000..909a8a90 --- /dev/null +++ b/src/libsysprof/sysprof-cpu-info.c @@ -0,0 +1,186 @@ +/* sysprof-cpu-info.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-cpu-info.h" + +struct _SysprofCpuInfo +{ + GObject parent_instance; + char *model_name; + guint id; + guint core_id; +}; + +enum { + PROP_0, + PROP_ID, + PROP_CORE_ID, + PROP_MODEL_NAME, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofCpuInfo, sysprof_cpu_info, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_cpu_info_finalize (GObject *object) +{ + SysprofCpuInfo *self = (SysprofCpuInfo *)object; + + g_clear_pointer (&self->model_name, g_free); + + G_OBJECT_CLASS (sysprof_cpu_info_parent_class)->finalize (object); +} + +static void +sysprof_cpu_info_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofCpuInfo *self = SYSPROF_CPU_INFO (object); + + switch (prop_id) + { + case PROP_ID: + g_value_set_uint (value, self->id); + break; + + case PROP_CORE_ID: + g_value_set_uint (value, self->core_id); + break; + + case PROP_MODEL_NAME: + g_value_set_string (value, self->model_name); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_cpu_info_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofCpuInfo *self = SYSPROF_CPU_INFO (object); + + switch (prop_id) + { + case PROP_ID: + self->id = g_value_get_uint (value); + break; + + case PROP_CORE_ID: + self->core_id = g_value_get_uint (value); + break; + + case PROP_MODEL_NAME: + self->model_name = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_cpu_info_class_init (SysprofCpuInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_cpu_info_finalize; + object_class->get_property = sysprof_cpu_info_get_property; + object_class->set_property = sysprof_cpu_info_set_property; + + properties[PROP_ID] = + g_param_spec_uint ("id", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_CORE_ID] = + g_param_spec_uint ("core-id", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MODEL_NAME] = + g_param_spec_string ("model-name", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_cpu_info_init (SysprofCpuInfo *self) +{ +} + +const char * +sysprof_cpu_info_get_model_name (SysprofCpuInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_CPU_INFO (self), NULL); + + return self->model_name; +} + +guint +sysprof_cpu_info_get_id (SysprofCpuInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_CPU_INFO (self), 0); + + return self->id; +} + +void +_sysprof_cpu_info_set_model_name (SysprofCpuInfo *self, + const char *model_name) +{ + g_return_if_fail (SYSPROF_IS_CPU_INFO (self)); + + if (g_set_str (&self->model_name, model_name)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL_NAME]); +} + +guint +sysprof_cpu_info_get_core_id (SysprofCpuInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_CPU_INFO (self), 0); + + return self->core_id; +} + +void +_sysprof_cpu_info_set_core_id (SysprofCpuInfo *self, + guint core_id) +{ + g_return_if_fail (SYSPROF_IS_CPU_INFO (self)); + + if (self->core_id != core_id) + { + self->core_id = core_id; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CORE_ID]); + } +} diff --git a/src/libsysprof/sysprof-capture-model.h b/src/libsysprof/sysprof-cpu-info.h similarity index 66% rename from src/libsysprof/sysprof-capture-model.h rename to src/libsysprof/sysprof-cpu-info.h index 71c2e227..2e66f5c9 100644 --- a/src/libsysprof/sysprof-capture-model.h +++ b/src/libsysprof/sysprof-cpu-info.h @@ -1,4 +1,4 @@ -/* sysprof-capture-model.h +/* sysprof-cpu-info.h * * Copyright 2023 Christian Hergert * @@ -20,19 +20,22 @@ #pragma once -#include +#include #include G_BEGIN_DECLS -#define SYSPROF_TYPE_CAPTURE_MODEL (sysprof_capture_model_get_type()) +#define SYSPROF_TYPE_CPU_INFO (sysprof_cpu_info_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofCaptureModel, sysprof_capture_model, SYSPROF, CAPTURE_MODEL, GObject) +G_DECLARE_FINAL_TYPE (SysprofCpuInfo, sysprof_cpu_info, SYSPROF, CPU_INFO, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofCaptureModel *sysprof_capture_model_new_from_fd (int capture_fd, - GError **error); +guint sysprof_cpu_info_get_id (SysprofCpuInfo *self); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_cpu_info_get_core_id (SysprofCpuInfo *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_cpu_info_get_model_name (SysprofCpuInfo *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-cpu-usage.c b/src/libsysprof/sysprof-cpu-usage.c new file mode 100644 index 00000000..229f9c8f --- /dev/null +++ b/src/libsysprof/sysprof-cpu-usage.c @@ -0,0 +1,401 @@ +/* sysprof-cpu-usage.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "line-reader-private.h" + +#include "sysprof-cpu-usage.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +#define PROC_STAT_BUF_SIZE (4096*4) + +struct _SysprofCpuUsage +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofCpuUsageClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofCpuUsage, sysprof_cpu_usage, SYSPROF_TYPE_INSTRUMENT) + +typedef struct _CpuInfo +{ + int counter_base; + double total; + glong last_user; + glong last_idle; + glong last_system; + glong last_nice; + glong last_iowait; + glong last_irq; + glong last_softirq; + glong last_steal; + glong last_guest; + glong last_guest_nice; +} CpuInfo; + +typedef struct _CpuFreq +{ + gint64 max; + int stat_fd; + char buf[116]; +} CpuFreq; + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; +} Record; + +static void +record_free (gpointer data) +{ + Record *record = data; + + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_free (record); +} + +static void +freq_info_clear (gpointer data) +{ + CpuFreq *cpu_freq = data; + g_clear_fd (&cpu_freq->stat_fd, NULL); +} + +static double +get_cpu_freq (int stat_fd, + guint cpu, + double max, + char *buf, + gssize len) +{ + gint64 val; + + if (stat_fd == -1) + return .0; + + if (len <= 0) + return .0; + + buf[len] = 0; + g_strchug (buf); + val = g_ascii_strtoll (buf, NULL, 10); + + val = CLAMP (val, .0, max); + + return (double)val / max * 100.; +} + +static DexFuture * +sysprof_cpu_usage_record_fiber (gpointer user_data) +{ + Record *record = user_data; + g_autoptr(GArray) cpu_info = NULL; + g_autoptr(GArray) freq_info = NULL; + g_autofd int stat_fd = -1; + g_autofree char *read_buffer = NULL; + g_autofree SysprofCaptureCounterValue *values = NULL; + g_autofree SysprofCaptureCounter *counters = NULL; + g_autofree guint *ids = NULL; + SysprofCaptureCounter *counter; + SysprofCaptureWriter *writer; + guint n_cpu; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + writer = _sysprof_recording_writer (record->recording); + n_cpu = g_get_num_processors (); + stat_fd = open ("/proc/stat", O_RDONLY|O_CLOEXEC); + g_unix_set_fd_nonblocking (stat_fd, TRUE, NULL); + read_buffer = g_malloc (PROC_STAT_BUF_SIZE); + + counters = g_new0 (SysprofCaptureCounter, (n_cpu * 2) + 1); + ids = g_new0 (guint, (n_cpu * 2) + 1); + values = g_new0 (SysprofCaptureCounterValue, (n_cpu * 2) + 1); + + cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo)); + g_array_set_size (cpu_info, n_cpu); + + freq_info = g_array_new (FALSE, TRUE, sizeof (CpuFreq)); + g_array_set_clear_func (freq_info, freq_info_clear); + + /* Create counter information for all of our counters that we will need + * to submit in upcoming parses. + */ + for (guint i = 0; i < n_cpu; i++) + { + g_autofree char *max_path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_max_freq", i); + g_autofree char *cur_path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i); + g_autofree char *max_value = NULL; + CpuFreq cf; + + ids[i*2] = sysprof_capture_writer_request_counter (writer, 1); + ids[i*2+1] = sysprof_capture_writer_request_counter (writer, 1); + + counter = &counters[i*2]; + counter->id = ids[i*2]; + counter->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; + counter->value.vdbl = 0; + g_strlcpy (counter->category, "CPU Percent", sizeof counter->category); + g_snprintf (counter->name, sizeof counter->name, "Total CPU %d", i); + g_snprintf (counter->description, sizeof counter->description, + "Total CPU usage %d", i); + + counter = &counters[i*2+1]; + counter->id = ids[i*2+1]; + counter->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; + counter->value.vdbl = 0; + g_strlcpy (counter->category, "CPU Frequency", sizeof counter->category); + g_snprintf (counter->name, sizeof counter->name, "CPU %d", i); + g_snprintf (counter->description, sizeof counter->description, + "Frequency of CPU %d", i); + + cf.stat_fd = open (cur_path, O_RDONLY|O_CLOEXEC); + g_unix_set_fd_nonblocking (cf.stat_fd, TRUE, NULL); + cf.buf[0] = 0; + if (g_file_get_contents (max_path, &max_value, NULL, NULL)) + cf.max = g_ascii_strtoll (max_value, NULL, 10); + else + cf.max = 0; + g_array_append_val (freq_info, cf); + } + + /* Now create our combined counter */ + ids[n_cpu*2] = sysprof_capture_writer_request_counter (writer, 1); + counter = &counters[n_cpu*2]; + counter->id = ids[n_cpu*2]; + counter->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; + counter->value.vdbl = 0; + g_strlcpy (counter->category, "CPU Percent", sizeof counter->category); + g_snprintf (counter->name, sizeof counter->name, "Combined"); + g_snprintf (counter->description, sizeof counter->description, "Combined CPU usage"); + + /* Register all the counters as a group */ + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + counters, + n_cpu * 2 + 1); + + for (;;) + { + g_autoptr(GPtrArray) futures = g_ptr_array_new_with_free_func (dex_unref); + g_autoptr(DexFuture) cpu_future = NULL; + LineReader reader; + glong total_usage = 0; + gsize line_len; + char *line; + + /* First collect all our reads and then wait for them to finish before + * parsing in a pass. With io_uring, this lets us coalesce all the lseek + * and reads into a single set of iops. + */ + for (guint i = 0; i < n_cpu; i++) + { + CpuFreq *cf = &g_array_index (freq_info, CpuFreq, i); + + g_ptr_array_add (futures, dex_aio_read (NULL, cf->stat_fd, cf->buf, sizeof cf->buf - 1, 0)); + } + cpu_future = dex_aio_read (NULL, stat_fd, read_buffer, PROC_STAT_BUF_SIZE, 0); + g_ptr_array_add (futures, dex_ref (cpu_future)); + if (!dex_await (dex_future_first (dex_ref (record->cancellable), + dex_future_allv ((DexFuture **)futures->pdata, futures->len), + NULL), + NULL)) + break; + + /* Now parse all the contents of the stat files which should be + * populated in the various files. + */ + line_reader_init (&reader, read_buffer, dex_await_int64 (dex_ref (cpu_future), NULL)); + while ((line = line_reader_next (&reader, &line_len))) + { + CpuInfo *ci; + char cpu[64]; + glong user; + glong sys; + glong nice; + glong idle; + int id; + int ret; + glong iowait; + glong irq; + glong softirq; + glong steal; + glong guest; + glong guest_nice; + glong user_calc; + glong system_calc; + glong nice_calc; + glong idle_calc; + glong iowait_calc; + glong irq_calc; + glong softirq_calc; + glong steal_calc; + glong guest_calc; + glong guest_nice_calc; + glong total; + + line[line_len] = 0; + + /* CPU lines come first, short-circuit after */ + if (!g_str_has_prefix (line, "cpu")) + break; + + /* First line is "cpu ..." */ + if (!g_ascii_isdigit (line[3])) + continue; + + /* Parse the various counters in order */ + user = nice = sys = idle = id = 0; + ret = sscanf (line, "%63s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", + cpu, &user, &nice, &sys, &idle, + &iowait, &irq, &softirq, &steal, &guest, &guest_nice); + if (ret != 11) + continue; + + /* Get the CPU identifier */ + ret = sscanf (cpu, "cpu%d", &id); + if (ret != 1 || id < 0 || id >= n_cpu) + continue; + + ci = &g_array_index (cpu_info, CpuInfo, id); + + user_calc = user - ci->last_user; + nice_calc = nice - ci->last_nice; + system_calc = sys - ci->last_system; + idle_calc = idle - ci->last_idle; + iowait_calc = iowait - ci->last_iowait; + irq_calc = irq - ci->last_irq; + softirq_calc = softirq - ci->last_softirq; + steal_calc = steal - ci->last_steal; + guest_calc = guest - ci->last_guest; + guest_nice_calc = guest_nice - ci->last_guest_nice; + + total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc; + ci->total = ((total - idle_calc) / (double)total) * 100.; + + ci->last_user = user; + ci->last_nice = nice; + ci->last_idle = idle; + ci->last_system = sys; + ci->last_iowait = iowait; + ci->last_irq = irq; + ci->last_softirq = softirq; + ci->last_steal = steal; + ci->last_guest = guest; + ci->last_guest_nice = guest_nice; + } + + /* Publish counters to the capture file */ + for (guint i = 0; i < n_cpu; i++) + { + const CpuInfo *ci = &g_array_index (cpu_info, CpuInfo, i); + CpuFreq *cf = &g_array_index (freq_info, CpuFreq, i); + DexFuture *freq_future = g_ptr_array_index (futures, i); + gssize len = dex_await_int64 (dex_ref (freq_future), NULL); + + values[i*2].vdbl = ci->total; + values[i*2+1].vdbl = get_cpu_freq (cf->stat_fd, i, cf->max, cf->buf, len); + + total_usage += ci->total; + } + + values[n_cpu*2].vdbl = total_usage / (double)n_cpu; + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ids, + values, + n_cpu * 2 + 1); + + /* Wait for cancellation or ⅕ second */ + dex_await (dex_future_first (dex_ref (record->cancellable), + dex_timeout_new_usec (G_USEC_PER_SEC / 5), + NULL), + NULL); + if (dex_future_get_status (record->cancellable) != DEX_FUTURE_STATUS_PENDING) + break; + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_cpu_usage_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_CPU_USAGE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_cpu_usage_record_fiber, + record, + record_free); +} + +static void +sysprof_cpu_usage_class_init (SysprofCpuUsageClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_cpu_usage_record; +} + +static void +sysprof_cpu_usage_init (SysprofCpuUsage *self) +{ +} + +SysprofInstrument * +sysprof_cpu_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_CPU_USAGE, NULL); +} diff --git a/src/libsysprof/sysprof-netdev-source.h b/src/libsysprof/sysprof-cpu-usage.h similarity index 50% rename from src/libsysprof/sysprof-netdev-source.h rename to src/libsysprof/sysprof-cpu-usage.h index 4fcc448a..2e980492 100644 --- a/src/libsysprof/sysprof-netdev-source.h +++ b/src/libsysprof/sysprof-cpu-usage.h @@ -1,6 +1,6 @@ -/* sysprof-netdev-source.h +/* sysprof-cpu-usage.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,16 +20,23 @@ #pragma once -#include "sysprof-source.h" +#include "sysprof-instrument.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_NETDEV_SOURCE (sysprof_netdev_source_get_type()) +#define SYSPROF_TYPE_CPU_USAGE (sysprof_cpu_usage_get_type()) +#define SYSPROF_IS_CPU_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_CPU_USAGE) +#define SYSPROF_CPU_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_CPU_USAGE, SysprofCpuUsage) +#define SYSPROF_CPU_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_CPU_USAGE, SysprofCpuUsageClass) + +typedef struct _SysprofCpuUsage SysprofCpuUsage; +typedef struct _SysprofCpuUsageClass SysprofCpuUsageClass; SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofNetdevSource, sysprof_netdev_source, SYSPROF, NETDEV_SOURCE, GObject) - +GType sysprof_cpu_usage_get_type (void) G_GNUC_CONST; SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_netdev_source_new (void); +SysprofInstrument *sysprof_cpu_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCpuUsage, g_object_unref) G_END_DECLS diff --git a/src/libsysprof/sysprof-descendants-model-private.h b/src/libsysprof/sysprof-descendants-model-private.h new file mode 100644 index 00000000..9560ce6b --- /dev/null +++ b/src/libsysprof/sysprof-descendants-model-private.h @@ -0,0 +1,35 @@ +/* sysprof-descendants-model-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-callgraph.h" +#include "sysprof-symbol.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DESCENDANTS_MODEL (sysprof_descendants_model_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofDescendantsModel, sysprof_descendants_model, SYSPROF, DESCENDANTS_MODEL, GObject) + +GListModel *_sysprof_descendants_model_new (SysprofCallgraph *callgraph, + SysprofSymbol *symbol); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-descendants-model.c b/src/libsysprof/sysprof-descendants-model.c new file mode 100644 index 00000000..c3b37654 --- /dev/null +++ b/src/libsysprof/sysprof-descendants-model.c @@ -0,0 +1,263 @@ +/* sysprof-descendants-model.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-callgraph-private.h" +#include "sysprof-callgraph-frame-private.h" +#include "sysprof-descendants-model-private.h" +#include "sysprof-document-private.h" +#include "sysprof-symbol-private.h" + +#define MAX_STACK_DEPTH 128 + +struct _SysprofDescendantsModel +{ + GObject parent_instance; + SysprofCallgraph *callgraph; + SysprofSymbol *symbol; + SysprofCallgraphNode root; +}; + +static GType +sysprof_descendants_model_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_CALLGRAPH_FRAME; +} + +static guint +sysprof_descendants_model_get_n_items (GListModel *model) +{ + return 1; +} + +static gpointer +sysprof_descendants_model_get_item (GListModel *model, + guint position) +{ + SysprofDescendantsModel *self = SYSPROF_DESCENDANTS_MODEL (model); + + if (position != 0) + return NULL; + + return _sysprof_callgraph_frame_new_for_node (self->callgraph, G_OBJECT (self), &self->root); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_descendants_model_get_item_type; + iface->get_n_items = sysprof_descendants_model_get_n_items; + iface->get_item = sysprof_descendants_model_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDescendantsModel, sysprof_descendants_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static void +sysprof_descendants_model_finalize (GObject *object) +{ + SysprofDescendantsModel *self = (SysprofDescendantsModel *)object; + + _sysprof_callgraph_node_free (&self->root, FALSE); + + g_clear_object (&self->callgraph); + g_clear_object (&self->symbol); + + G_OBJECT_CLASS (sysprof_descendants_model_parent_class)->finalize (object); +} + +static void +sysprof_descendants_model_class_init (SysprofDescendantsModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_descendants_model_finalize; +} + +static void +sysprof_descendants_model_init (SysprofDescendantsModel *self) +{ +} + +static SysprofCallgraphNode * +sysprof_descendants_model_add_trace (SysprofDescendantsModel *self, + SysprofSymbol **symbols, + guint n_symbols) +{ + SysprofCallgraphNode *parent = NULL; + + g_assert (SYSPROF_IS_DESCENDANTS_MODEL (self)); + g_assert (symbols != NULL); + g_assert (n_symbols > 0); + + parent = &self->root; + + for (guint i = n_symbols; i > 0; i--) + { + SysprofSymbol *symbol = symbols[i-1]; + SysprofCallgraphNode *node = NULL; + SysprofCallgraphSummary *summary; + + /* Try to find @symbol within the children of @parent */ + for (SysprofCallgraphNode *iter = parent->children; + iter != NULL; + iter = iter->next) + { + g_assert (iter != NULL); + g_assert (iter->summary != NULL); + g_assert (iter->summary->symbol != NULL); + g_assert (symbol != NULL); + + if (_sysprof_symbol_equal (iter->summary->symbol, symbol)) + { + node = iter; + goto next_symbol; + } + } + + if (!(summary = g_hash_table_lookup (self->callgraph->symbol_to_summary, symbol))) + { + node = parent; + goto next_symbol; + } + + /* Otherwise create a new node */ + node = g_new0 (SysprofCallgraphNode, 1); + node->summary = summary; + node->parent = parent; + node->next = parent->children; + if (parent->children) + parent->children->prev = node; + parent->children = node; + + g_assert (node->summary != NULL); + + next_symbol: + parent = node; + } + + return parent; +} + +static void +sysprof_descendants_model_add_traceable (SysprofDescendantsModel *self, + SysprofDocument *document, + SysprofDocumentTraceable *traceable, + SysprofSymbol *from_symbol, + gboolean include_threads) +{ + SysprofAddressContext final_context; + SysprofSymbol **symbols; + SysprofSymbolKind kind; + guint stack_depth; + guint n_symbols; + + g_assert (SYSPROF_IS_DESCENDANTS_MODEL (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + g_assert (SYSPROF_IS_SYMBOL (from_symbol)); + + stack_depth = MIN (MAX_STACK_DEPTH, sysprof_document_traceable_get_stack_depth (traceable)); + symbols = g_alloca (sizeof (SysprofSymbol *) * (stack_depth + 2)); + n_symbols = sysprof_document_symbolize_traceable (document, traceable, symbols, stack_depth, &final_context); + + kind = sysprof_symbol_get_kind (from_symbol); + + if (kind == SYSPROF_SYMBOL_KIND_PROCESS || kind == SYSPROF_SYMBOL_KIND_THREAD) + { + int pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable)); + + if (include_threads) + { + int thread_id = sysprof_document_traceable_get_thread_id (traceable); + symbols[n_symbols++] = _sysprof_document_thread_symbol (document, pid, thread_id); + } + + symbols[n_symbols++] = _sysprof_document_process_symbol (document, pid); + } + + for (guint i = n_symbols; i > 0; i--) + { + SysprofSymbol *symbol = symbols[i-1]; + + n_symbols--; + + if (_sysprof_symbol_equal (symbol, from_symbol)) + break; + } + + if (n_symbols > 0) + { + SysprofCallgraphNode *node; + + node = sysprof_descendants_model_add_trace (self, symbols, n_symbols); + + node->is_toplevel = TRUE; + node->count++; + + if (node && self->callgraph->augment_func) + self->callgraph->augment_func (self->callgraph, + node, + SYSPROF_DOCUMENT_FRAME (traceable), + FALSE, + self->callgraph->augment_func_data); + + if ((self->callgraph->flags & SYSPROF_CALLGRAPH_FLAGS_CATEGORIZE_FRAMES) != 0) + _sysprof_callgraph_categorize (self->callgraph, node); + } +} + +GListModel * +_sysprof_descendants_model_new (SysprofCallgraph *callgraph, + SysprofSymbol *symbol) +{ + SysprofDescendantsModel *self; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) model = NULL; + gboolean include_threads; + guint n_items; + + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH (callgraph), NULL); + g_return_val_if_fail (SYSPROF_IS_SYMBOL (symbol), NULL); + + model = sysprof_callgraph_list_traceables_for_symbol (callgraph, symbol); + document = g_object_ref (callgraph->document); + + self = g_object_new (SYSPROF_TYPE_DESCENDANTS_MODEL, NULL); + self->callgraph = g_object_ref (callgraph); + self->symbol = g_object_ref (symbol); + self->root.summary = g_hash_table_lookup (callgraph->symbol_to_summary, symbol); + + g_assert (self->root.summary != NULL); + g_assert (_sysprof_symbol_equal (self->root.summary->symbol, symbol)); + + include_threads = (callgraph->flags & SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS) != 0; + n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (model, i); + + sysprof_descendants_model_add_traceable (self, document, traceable, symbol, include_threads); + } + + return G_LIST_MODEL (self); +} diff --git a/src/libsysprof/sysprof-diagnostic-private.h b/src/libsysprof/sysprof-diagnostic-private.h new file mode 100644 index 00000000..50e65054 --- /dev/null +++ b/src/libsysprof/sysprof-diagnostic-private.h @@ -0,0 +1,31 @@ +/* sysprof-diagnostic-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-diagnostic.h" + +G_BEGIN_DECLS + +SysprofDiagnostic *_sysprof_diagnostic_new (char *domain, + char *message, + gboolean fatal); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-diagnostic.c b/src/libsysprof/sysprof-diagnostic.c new file mode 100644 index 00000000..7aa5f099 --- /dev/null +++ b/src/libsysprof/sysprof-diagnostic.c @@ -0,0 +1,151 @@ +/* sysprof-diagnostic.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-diagnostic-private.h" + +struct _SysprofDiagnostic +{ + GObject parent_instance; + GRefString *domain; + char *message; + guint fatal : 1; +}; + +enum { + PROP_0, + PROP_DOMAIN, + PROP_MESSAGE, + PROP_FATAL, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDiagnostic, sysprof_diagnostic, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_diagnostic_finalize (GObject *object) +{ + SysprofDiagnostic *self = (SysprofDiagnostic *)object; + + g_clear_pointer (&self->domain, g_free); + g_clear_pointer (&self->message, g_free); + + G_OBJECT_CLASS (sysprof_diagnostic_parent_class)->finalize (object); +} + +static void +sysprof_diagnostic_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDiagnostic *self = SYSPROF_DIAGNOSTIC (object); + + switch (prop_id) + { + case PROP_DOMAIN: + g_value_set_string (value, self->domain); + break; + + case PROP_MESSAGE: + g_value_set_string (value, self->message); + break; + + case PROP_FATAL: + g_value_set_boolean (value, self->fatal); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_diagnostic_class_init (SysprofDiagnosticClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_diagnostic_finalize; + object_class->get_property = sysprof_diagnostic_get_property; + + properties [PROP_DOMAIN] = + g_param_spec_string ("domain", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MESSAGE] = + g_param_spec_string ("message", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FATAL] = + g_param_spec_boolean ("fatal", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_diagnostic_init (SysprofDiagnostic *self) +{ +} + +const char * +sysprof_diagnostic_get_domain (SysprofDiagnostic *self) +{ + g_return_val_if_fail (SYSPROF_IS_DIAGNOSTIC (self), NULL); + + return self->domain; +} + +const char * +sysprof_diagnostic_get_message (SysprofDiagnostic *self) +{ + g_return_val_if_fail (SYSPROF_IS_DIAGNOSTIC (self), NULL); + + return self->message; +} + +gboolean +sysprof_diagnostic_get_fatal (SysprofDiagnostic *self) +{ + g_return_val_if_fail (SYSPROF_IS_DIAGNOSTIC (self), FALSE); + + return self->fatal; +} + +SysprofDiagnostic * +_sysprof_diagnostic_new (char *domain, + char *message, + gboolean fatal) +{ + SysprofDiagnostic *self; + + self = g_object_new (SYSPROF_TYPE_DIAGNOSTIC, NULL); + self->message = message; + self->domain = domain; + self->fatal = !!fatal; + + return self; +} diff --git a/src/libsysprof/sysprof-hostinfo-source.h b/src/libsysprof/sysprof-diagnostic.h similarity index 59% rename from src/libsysprof/sysprof-hostinfo-source.h rename to src/libsysprof/sysprof-diagnostic.h index 09d21d61..3c265b65 100644 --- a/src/libsysprof/sysprof-hostinfo-source.h +++ b/src/libsysprof/sysprof-diagnostic.h @@ -1,6 +1,6 @@ -/* sysprof-hostinfo-source.h +/* sysprof-diagnostic.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,20 +20,22 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif +#include -#include "sysprof-source.h" +#include G_BEGIN_DECLS -#define SYSPROF_TYPE_HOSTINFO_SOURCE (sysprof_hostinfo_source_get_type()) +#define SYSPROF_TYPE_DIAGNOSTIC (sysprof_diagnostic_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofHostinfoSource, sysprof_hostinfo_source, SYSPROF, HOSTINFO_SOURCE, GObject) +G_DECLARE_FINAL_TYPE (SysprofDiagnostic, sysprof_diagnostic, SYSPROF, DIAGNOSTIC, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_hostinfo_source_new (void); +const char *sysprof_diagnostic_get_domain (SysprofDiagnostic *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_diagnostic_get_message (SysprofDiagnostic *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_diagnostic_get_fatal (SysprofDiagnostic *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-disk-usage.c b/src/libsysprof/sysprof-disk-usage.c new file mode 100644 index 00000000..bd66a984 --- /dev/null +++ b/src/libsysprof/sysprof-disk-usage.c @@ -0,0 +1,442 @@ +/* sysprof-disk-usage.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include + +#include "sysprof-disk-usage.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +#include "line-reader-private.h" + +#define ADD_FROM_CHAR(v, c) (((v)*10L)+((c)-'0')) + +struct _SysprofDiskUsage +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofDiskUsageClass +{ + SysprofInstrumentClass parent_class; +}; + +typedef struct _DiskUsage +{ + /* Counter IDs */ + guint reads_total_id; + guint writes_total_id; + + /* Index where values are stored */ + guint values_at; + + char device[32]; + + gint64 reads_total; + gint64 reads_merged; + gint64 reads_sectors; + gint64 reads_msec; + gint64 writes_total; + gint64 writes_merged; + gint64 writes_sectors; + gint64 writes_msec; + gint64 iops_active; + gint64 iops_msec; + gint64 iops_msec_weighted; +} DiskUsage; + +typedef enum _Column +{ + /* -2 */ COLUMN_MAJOR, + /* -1 */ COLUMN_MINOR, + /* 0 */ COLUMN_NAME, + /* 1 */ COLUMN_READS_TOTAL, + /* 2 */ COLUMN_READS_MERGED, + /* 3 */ COLUMN_READS_SECTORS, + /* 4 */ COLUMN_READS_MSEC, + /* 5 */ COLUMN_WRITES_TOTAL, + /* 6 */ COLUMN_WRITES_MERGED, + /* 7 */ COLUMN_WRITES_SECTORS, + /* 8 */ COLUMN_WRITES_MSEC, + /* 9 */ COLUMN_IOPS_ACTIVE, + /* 10 */ COLUMN_IOPS_MSEC, + /* 11 */ COLUMN_IOPS_MSEC_WEIGHTED, +} Column; + +G_DEFINE_FINAL_TYPE (SysprofDiskUsage, sysprof_disk_usage, SYSPROF_TYPE_INSTRUMENT) + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; + GArray *devices; + GArray *ids; + GArray *values; +} Record; + +static void +record_free (gpointer data) +{ + Record *record = data; + + g_clear_object (&record->recording); + g_clear_pointer (&record->devices, g_array_unref); + g_clear_pointer (&record->ids, g_array_unref); + g_clear_pointer (&record->values, g_array_unref); + dex_clear (&record->cancellable); + g_free (record); +} + +static DiskUsage * +register_counters_by_name (Record *record, + const char *name) +{ + static const SysprofCaptureCounterValue zeroval = {0}; + SysprofCaptureCounter ctr[2] = {0}; + SysprofCaptureWriter *writer; + DiskUsage ds = {0}; + + g_assert (record != NULL); + g_assert (name != NULL); + + writer = _sysprof_recording_writer (record->recording); + + ds.values_at = record->ids->len; + ds.reads_total_id = sysprof_capture_writer_request_counter (writer, 1); + ds.writes_total_id = sysprof_capture_writer_request_counter (writer, 1); + + g_strlcpy (ds.device, name, sizeof ds.device); + + g_strlcpy (ctr[0].category, "Disk", sizeof ctr[0].category); + g_snprintf (ctr[0].name, sizeof ctr[0].name, "Total Reads (%s)", name); + g_strlcpy (ctr[0].description, name, sizeof ctr[0].description); + ctr[0].id = ds.reads_total_id; + ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[0].value.v64 = 0; + + g_strlcpy (ctr[1].category, "Disk", sizeof ctr[1].category); + g_snprintf (ctr[1].name, sizeof ctr[1].name, "Total Writes (%s)", name); + g_strlcpy (ctr[1].description, name, sizeof ctr[1].description); + ctr[1].id = ds.writes_total_id; + ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[1].value.v64 = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctr, + G_N_ELEMENTS (ctr)); + + g_array_append_val (record->devices, ds); + g_array_append_val (record->ids, ds.reads_total_id); + g_array_append_val (record->ids, ds.writes_total_id); + g_array_append_val (record->values, zeroval); + g_array_append_val (record->values, zeroval); + + return &g_array_index (record->devices, DiskUsage, record->devices->len-1); +} + +static DiskUsage * +find_device_by_name (Record *record, + const char *name) +{ + g_assert (record != NULL); + g_assert (record->devices != NULL); + g_assert (name != NULL); + + for (guint i = 0; i < record->devices->len; i++) + { + DiskUsage *ds = &g_array_index (record->devices, DiskUsage, i); + + if (strcmp (name, ds->device) == 0) + return ds; + } + + return NULL; +} + +static DexFuture * +sysprof_disk_usage_record_fiber (gpointer user_data) +{ + g_autoptr(GByteArray) buf = NULL; + Record *record = user_data; + SysprofCaptureWriter *writer; + g_autofd int stat_fd = -1; + LineReader reader; + DiskUsage *combined; + gint64 combined_reads_total = 0; + gint64 combined_writes_total = 0; + gboolean skip_publish = TRUE; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_CANCELLABLE (record->cancellable)); + + buf = g_byte_array_new (); + g_byte_array_set_size (buf, 4096*4); + + if (-1 == (stat_fd = open ("/proc/diskstats", O_RDONLY|O_CLOEXEC))) + return dex_future_new_for_errno (errno); + + writer = _sysprof_recording_writer (record->recording); + + register_counters_by_name (record, "Combined"); + + for (;;) + { + g_autoptr(DexFuture) read_future = NULL; + gssize n_read; + char *line; + gsize line_len; + + /* Read a new copy of our diskstats or bail from our + * recording loop. If cancellation future rejects, then + * we also break out of our recording loop. + */ + read_future = dex_aio_read (NULL, stat_fd, buf->data, buf->len-1, 0); + if (!dex_await (dex_future_first (dex_ref (record->cancellable), + dex_ref (read_future), + NULL), + NULL)) + break; + + n_read = dex_await_int64 (dex_ref (read_future), NULL); + if (n_read < 0) + break; + + line_reader_init (&reader, (char *)buf->data, n_read); + while ((line = line_reader_next (&reader, &line_len))) + { + DiskUsage ds = {0}; + DiskUsage *found; + gint64 dummy = 0; + int column = COLUMN_MAJOR; + + line[line_len] = 0; + + /* Skip past initial space */ + while (g_ascii_isspace (*line)) + line++; + + for (const char *ptr = line; *ptr; ptr++) + { + char ch; + + /* Skip past space and advance to next column */ + if (g_ascii_isspace (*ptr)) + { + while (g_ascii_isspace (*ptr)) + ptr++; + column++; + } + + ch = *ptr; + + switch (column) + { + case COLUMN_MAJOR: + case COLUMN_MINOR: + default: + dummy = ADD_FROM_CHAR (dummy, ch); + break; + + case COLUMN_NAME: + { + guint j; + + for (j = 0; j < sizeof ds.device && ds.device[j] != 0; j++) { /* Do Nothing */ } + if (j < sizeof ds.device) + ds.device[j] = ch; + ds.device[sizeof ds.device - 1] = 0; + + break; + } + + case COLUMN_READS_TOTAL: + ds.reads_total = ADD_FROM_CHAR (ds.reads_total, ch); + break; + + case COLUMN_READS_MERGED: + ds.reads_merged = ADD_FROM_CHAR (ds.reads_merged, ch); + break; + + case COLUMN_READS_SECTORS: + ds.reads_sectors = ADD_FROM_CHAR (ds.reads_sectors, ch); + break; + + case COLUMN_READS_MSEC: + ds.reads_msec = ADD_FROM_CHAR (ds.reads_msec, ch); + break; + + case COLUMN_WRITES_TOTAL: + ds.writes_total = ADD_FROM_CHAR (ds.writes_total, ch); + break; + + case COLUMN_WRITES_MERGED: + ds.writes_merged = ADD_FROM_CHAR (ds.writes_merged, ch); + break; + + case COLUMN_WRITES_SECTORS: + ds.writes_sectors = ADD_FROM_CHAR (ds.writes_sectors, ch); + break; + + case COLUMN_WRITES_MSEC: + ds.writes_msec = ADD_FROM_CHAR (ds.writes_msec, ch); + break; + + case COLUMN_IOPS_ACTIVE: + ds.iops_active = ADD_FROM_CHAR (ds.iops_active, ch); + break; + + case COLUMN_IOPS_MSEC: + ds.iops_msec = ADD_FROM_CHAR (ds.iops_msec, ch); + break; + + case COLUMN_IOPS_MSEC_WEIGHTED: + ds.iops_msec_weighted = ADD_FROM_CHAR (ds.iops_msec_weighted, ch); + break; + } + } + + g_strstrip (ds.device); + + if (ds.device[0]) + { + guint p; + gint64 reads_total; + gint64 writes_total; + + if (!(found = find_device_by_name (record, ds.device))) + found = register_counters_by_name (record, ds.device); + + /* Calculate new value, based on diff from previous */ + reads_total = ds.reads_total - found->reads_total; + writes_total = ds.writes_total - found->writes_total; + + /* Update value for publishing */ + p = found->values_at; + g_array_index (record->values, SysprofCaptureCounterValue, p).v64 = reads_total; + g_array_index (record->values, SysprofCaptureCounterValue, p+1).v64 = writes_total; + + /* Update combined values */ + combined_reads_total += reads_total; + combined_writes_total += writes_total; + + /* Save current value for diff on next poll */ + found->reads_total = ds.reads_total; + found->writes_total = ds.writes_total; + } + } + + if ((combined = find_device_by_name (record, "Combined"))) + { + /* TODO: It would be nice to not double count disk ops multiple + * times based on the parition, etc. + * + * For example: nvme0n1 nvme0n1p1 nvme0n1p2 nvme0n1p3 may get + * accounted multiple times even though they are all nvme0n1. + * + * The other option, is to just not do "Combined" counters. + */ + + g_array_index (record->values, + SysprofCaptureCounterValue, + combined->values_at).v64 + = combined_reads_total - combined->reads_total; + g_array_index (record->values, + SysprofCaptureCounterValue, + combined->values_at+1).v64 + = combined_writes_total - combined->writes_total; + + combined->reads_total = combined_reads_total; + combined->writes_total = combined_writes_total; + } + + g_assert (record->ids->len == record->values->len); + + if (skip_publish) + skip_publish = FALSE; + else + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + (const guint *)(gpointer)record->ids->data, + (const SysprofCaptureCounterValue *)(gpointer)record->values->data, + record->ids->len); + + dex_await (dex_future_first (dex_ref (record->cancellable), + dex_timeout_new_usec (G_USEC_PER_SEC / 2), + NULL), + NULL); + if (dex_future_get_status (record->cancellable) != DEX_FUTURE_STATUS_PENDING) + break; + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_disk_usage_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + record->devices = g_array_new (FALSE, FALSE, sizeof (DiskUsage)); + record->ids = g_array_new (FALSE, FALSE, sizeof (guint)); + record->values = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounterValue)); + + return dex_scheduler_spawn (NULL, 0, + sysprof_disk_usage_record_fiber, + record, + record_free); +} + +static void +sysprof_disk_usage_class_init (SysprofDiskUsageClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_disk_usage_record; +} + +static void +sysprof_disk_usage_init (SysprofDiskUsage *self) +{ +} + +SysprofInstrument * +sysprof_disk_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_DISK_USAGE, NULL); +} diff --git a/src/libsysprof/sysprof-disk-usage.h b/src/libsysprof/sysprof-disk-usage.h new file mode 100644 index 00000000..af6192e9 --- /dev/null +++ b/src/libsysprof/sysprof-disk-usage.h @@ -0,0 +1,42 @@ +/* sysprof-disk-usage.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DISK_USAGE (sysprof_disk_usage_get_type()) +#define SYSPROF_IS_DISK_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DISK_USAGE) +#define SYSPROF_DISK_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DISK_USAGE, SysprofDiskUsage) +#define SYSPROF_DISK_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DISK_USAGE, SysprofDiskUsageClass) + +typedef struct _SysprofDiskUsage SysprofDiskUsage; +typedef struct _SysprofDiskUsageClass SysprofDiskUsageClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_disk_usage_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_disk_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDiskUsage, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-diskstat-source.c b/src/libsysprof/sysprof-diskstat-source.c deleted file mode 100644 index 2256a8be..00000000 --- a/src/libsysprof/sysprof-diskstat-source.c +++ /dev/null @@ -1,472 +0,0 @@ -/* sysprof-diskstat-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-diskstat-source" - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "sysprof-backport-autocleanups.h" -#include "sysprof-line-reader.h" -#include "sysprof-diskstat-source.h" -#include "sysprof-helpers.h" - -#define ADD_FROM_CHAR(v, c) (((v)*10L)+((c)-'0')) - -struct _SysprofDiskstatSource -{ - GObject parent_instance; - - SysprofCaptureWriter *writer; - GArray *diskstats; - - /* FD for /proc/net/dev contents */ - gint diskstat_fd; - - /* GSource ID for polling */ - guint poll_source; - - guint ignore_next_poll : 1; -}; - -typedef struct -{ - /* Counter IDs */ - guint reads_total_id; - guint writes_total_id; - - gchar device[32]; - gint64 reads_total; - gint64 reads_merged; - gint64 reads_sectors; - gint64 reads_msec; - gint64 writes_total; - gint64 writes_merged; - gint64 writes_sectors; - gint64 writes_msec; - gint64 iops_active; - gint64 iops_msec; - gint64 iops_msec_weighted; -} Diskstat; - -enum { - /* -2 */ COLUMN_MAJOR, - /* -1 */ COLUMN_MINOR, - /* 0 */ COLUMN_NAME, - /* 1 */ COLUMN_READS_TOTAL, - /* 2 */ COLUMN_READS_MERGED, - /* 3 */ COLUMN_READS_SECTORS, - /* 4 */ COLUMN_READS_MSEC, - /* 5 */ COLUMN_WRITES_TOTAL, - /* 6 */ COLUMN_WRITES_MERGED, - /* 7 */ COLUMN_WRITES_SECTORS, - /* 8 */ COLUMN_WRITES_MSEC, - /* 9 */ COLUMN_IOPS_ACTIVE, - /* 10 */ COLUMN_IOPS_MSEC, - /* 11 */ COLUMN_IOPS_MSEC_WEIGHTED, -}; - -static void source_iface_init (SysprofSourceInterface *); - -G_DEFINE_TYPE_WITH_CODE (SysprofDiskstatSource, sysprof_diskstat_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static Diskstat * -find_device_by_name (SysprofDiskstatSource *self, - const gchar *name) -{ - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - g_assert (self->writer != NULL); - g_assert (name != NULL); - - for (guint i = 0; i < self->diskstats->len; i++) - { - Diskstat *diskstat = &g_array_index (self->diskstats, Diskstat, i); - - if (strcmp (name, diskstat->device) == 0) - return diskstat; - } - - return NULL; -} - -static Diskstat * -register_counters_by_name (SysprofDiskstatSource *self, - const gchar *name) -{ - SysprofCaptureCounter ctr[2] = {{{0}}}; - Diskstat ds = {0}; - - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - g_assert (name != NULL); - g_assert (self->writer != NULL); - - ds.reads_total_id = sysprof_capture_writer_request_counter (self->writer, 1); - ds.writes_total_id = sysprof_capture_writer_request_counter (self->writer, 1); - - g_strlcpy (ds.device, name, sizeof ds.device); - - g_strlcpy (ctr[0].category, "Disk", sizeof ctr[0].category); - g_snprintf (ctr[0].name, sizeof ctr[0].name, "Total Reads (%s)", name); - g_strlcpy (ctr[0].description, name, sizeof ctr[0].description); - ctr[0].id = ds.reads_total_id; - ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; - ctr[0].value.v64 = 0; - - g_strlcpy (ctr[1].category, "Disk", sizeof ctr[1].category); - g_snprintf (ctr[1].name, sizeof ctr[1].name, "Total Writes (%s)", name); - g_strlcpy (ctr[1].description, name, sizeof ctr[1].description); - ctr[1].id = ds.writes_total_id; - ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; - ctr[1].value.v64 = 1; - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - ctr, G_N_ELEMENTS (ctr)); - - g_array_append_val (self->diskstats, ds); - return &g_array_index (self->diskstats, Diskstat, self->diskstats->len - 1); -} - -static gboolean -sysprof_diskstat_source_get_is_ready (SysprofSource *source) -{ - return TRUE; -} - -static gboolean -sysprof_diskstat_source_poll_cb (gpointer data) -{ - g_autoptr(SysprofLineReader) reader = NULL; - SysprofDiskstatSource *self = data; - g_autoptr(GArray) counters = NULL; - g_autoptr(GArray) values = NULL; - gchar buf[4096*4]; - Diskstat *found; - gint64 combined_reads_total = 0; - gint64 combined_writes_total = 0; - gssize len; - gsize line_len; - gchar *line; - - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - - if (self->diskstat_fd == -1) - { - self->poll_source = 0; - return G_SOURCE_REMOVE; - } - - /* Seek to 0 forces reload of data */ - lseek (self->diskstat_fd, 0, SEEK_SET); - - len = read (self->diskstat_fd, buf, sizeof buf - 1); - - /* Bail for now unless we read enough data */ - if (len > 0) - buf[len] = 0; - else - return G_SOURCE_CONTINUE; - - counters = g_array_new (FALSE, FALSE, sizeof (guint)); - values = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounterValue)); - reader = sysprof_line_reader_new (buf, len); - -#if 0 -Entries looks like this... ------------------------------------------------------------------------------------------------------------------------------- - 259 0 nvme0n1 455442 37 15274065 82579 714090 524038 44971827 1078532 0 244176 957543 0 0 0 0 - 259 1 nvme0n1p1 403 0 10411 102 61 39 33171 72 0 61 53 0 0 0 0 - 259 2 nvme0n1p2 138 0 9594 29 7 1 64 20 0 49 24 0 0 0 0 - 259 3 nvme0n1p3 454820 37 15249700 82432 688488 523998 44938592 1038914 0 237085 924545 0 0 0 0 - 253 0 dm-0 454789 0 15246924 107870 1238001 0 44938592 7873687 0 248354 7981557 0 0 0 0 - 253 1 dm-1 92473 0 4824170 27656 179001 0 5876760 1378161 0 45291 1405817 0 0 0 0 - 253 2 dm-2 206 0 5552 100 240 0 1920 297 0 219 397 0 0 0 0 - 253 3 dm-3 362064 0 10414850 80861 1058760 0 39245920 6496983 0 206426 6577844 0 0 0 0 ------------------------------------------------------------------------------------------------------------------------------- -#endif - - while ((line = (gchar *)sysprof_line_reader_next (reader, &line_len))) - { - Diskstat ds = {0}; - gint64 dummy = 0; - gint column = COLUMN_MAJOR; - - line[line_len] = 0; - - /* Skip past initial space */ - while (g_ascii_isspace (*line)) - line++; - - for (const gchar *ptr = line; *ptr; ptr++) - { - gchar ch; - - /* Skip past space and advance to next column */ - if (g_ascii_isspace (*ptr)) - { - while (g_ascii_isspace (*ptr)) - ptr++; - column++; - } - - ch = *ptr; - - switch (column) - { - case COLUMN_MAJOR: - case COLUMN_MINOR: - default: - dummy = ADD_FROM_CHAR (dummy, ch); - break; - - case COLUMN_NAME: - { - guint j; - - for (j = 0; j < sizeof ds.device && ds.device[j] != 0; j++) { /* Do Nothing */ } - if (j < sizeof ds.device) - ds.device[j] = ch; - ds.device[sizeof ds.device - 1] = 0; - - break; - } - - case COLUMN_READS_TOTAL: - ds.reads_total = ADD_FROM_CHAR (ds.reads_total, ch); - break; - - case COLUMN_READS_MERGED: - ds.reads_merged = ADD_FROM_CHAR (ds.reads_merged, ch); - break; - - case COLUMN_READS_SECTORS: - ds.reads_sectors = ADD_FROM_CHAR (ds.reads_sectors, ch); - break; - - case COLUMN_READS_MSEC: - ds.reads_msec = ADD_FROM_CHAR (ds.reads_msec, ch); - break; - - case COLUMN_WRITES_TOTAL: - ds.writes_total = ADD_FROM_CHAR (ds.writes_total, ch); - break; - - case COLUMN_WRITES_MERGED: - ds.writes_merged = ADD_FROM_CHAR (ds.writes_merged, ch); - break; - - case COLUMN_WRITES_SECTORS: - ds.writes_sectors = ADD_FROM_CHAR (ds.writes_sectors, ch); - break; - - case COLUMN_WRITES_MSEC: - ds.writes_msec = ADD_FROM_CHAR (ds.writes_msec, ch); - break; - - case COLUMN_IOPS_ACTIVE: - ds.iops_active = ADD_FROM_CHAR (ds.iops_active, ch); - break; - - case COLUMN_IOPS_MSEC: - ds.iops_msec = ADD_FROM_CHAR (ds.iops_msec, ch); - break; - - case COLUMN_IOPS_MSEC_WEIGHTED: - ds.iops_msec_weighted = ADD_FROM_CHAR (ds.iops_msec_weighted, ch); - break; - } - } - - g_strstrip (ds.device); - - if (ds.device[0]) - { - gint64 reads_total; - gint64 writes_total; - - if (!(found = find_device_by_name (self, ds.device))) - found = register_counters_by_name (self, ds.device); - - /* Stash new value, based on diff from previous */ - reads_total = ds.reads_total - found->reads_total; - writes_total = ds.writes_total - found->writes_total; - - g_array_append_val (counters, found->reads_total_id); - g_array_append_val (values, reads_total); - - g_array_append_val (counters, found->writes_total_id); - g_array_append_val (values, writes_total); - - /* Update combined values */ - combined_reads_total += reads_total; - combined_writes_total += writes_total; - - /* Save current value for diff on next poll */ - found->reads_total = ds.reads_total; - found->writes_total = ds.writes_total; - } - } - - if (!(found = find_device_by_name (self, "Combined"))) - found = register_counters_by_name (self, "Combined"); - - g_array_append_val (counters, found->reads_total_id); - g_array_append_val (values, combined_reads_total); - - g_array_append_val (counters, found->writes_total_id); - g_array_append_val (values, combined_writes_total); - - if (self->ignore_next_poll) - self->ignore_next_poll = FALSE; - else - sysprof_capture_writer_set_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - (const guint *)(gpointer)counters->data, - (const SysprofCaptureCounterValue *)(gpointer)values->data, - counters->len); - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_diskstat_source_prepare (SysprofSource *source) -{ - SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - - self->diskstat_fd = g_open ("/proc/diskstats", O_RDONLY, 0); - - if (self->diskstat_fd == -1) - { - int errsv = errno; - error = g_error_new (G_FILE_ERROR, - g_file_error_from_errno (errsv), - "%s", - g_strerror (errsv)); - sysprof_source_emit_failed (source, error); - return; - } - - self->ignore_next_poll = TRUE; - sysprof_diskstat_source_poll_cb (self); - - sysprof_source_emit_ready (source); -} - -static void -sysprof_diskstat_source_start (SysprofSource *source) -{ - SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; - - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - - self->poll_source = g_timeout_add (200, sysprof_diskstat_source_poll_cb, self); - - /* Poll immediately */ - sysprof_diskstat_source_poll_cb (self); -} - -static void -sysprof_diskstat_source_stop (SysprofSource *source) -{ - SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; - - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - - /* Poll one last time */ - sysprof_diskstat_source_poll_cb (self); - - g_clear_handle_id (&self->poll_source, g_source_remove); - - sysprof_source_emit_finished (source); -} - -static void -sysprof_diskstat_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofDiskstatSource *self = (SysprofDiskstatSource *)source; - - g_assert (SYSPROF_IS_DISKSTAT_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->get_is_ready = sysprof_diskstat_source_get_is_ready; - iface->prepare = sysprof_diskstat_source_prepare; - iface->set_writer = sysprof_diskstat_source_set_writer; - iface->start = sysprof_diskstat_source_start; - iface->stop = sysprof_diskstat_source_stop; -} - -static void -sysprof_diskstat_source_finalize (GObject *object) -{ - SysprofDiskstatSource *self = (SysprofDiskstatSource *)object; - - g_clear_pointer (&self->diskstats, g_array_unref); - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - - if (self->diskstat_fd != -1) - { - close (self->diskstat_fd); - self->diskstat_fd = -1; - } - - G_OBJECT_CLASS (sysprof_diskstat_source_parent_class)->finalize (object); -} - -static void -sysprof_diskstat_source_class_init (SysprofDiskstatSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_diskstat_source_finalize; -} - -static void -sysprof_diskstat_source_init (SysprofDiskstatSource *self) -{ - self->diskstats = g_array_new (FALSE, FALSE, sizeof (Diskstat)); -} - -SysprofSource * -sysprof_diskstat_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_DISKSTAT_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-diskstat-source.h b/src/libsysprof/sysprof-diskstat-source.h deleted file mode 100644 index 58386e7a..00000000 --- a/src/libsysprof/sysprof-diskstat-source.h +++ /dev/null @@ -1,35 +0,0 @@ -/* sysprof-diskstat-source.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-source.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_DISKSTAT_SOURCE (sysprof_diskstat_source_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofDiskstatSource, sysprof_diskstat_source, SYSPROF, DISKSTAT_SOURCE, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_diskstat_source_new (void); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-document-allocation.c b/src/libsysprof/sysprof-document-allocation.c new file mode 100644 index 00000000..c64a549b --- /dev/null +++ b/src/libsysprof/sysprof-document-allocation.c @@ -0,0 +1,258 @@ +/* sysprof-document-allocation.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-frame-private.h" + +#include "sysprof-document-allocation.h" +#include "sysprof-document-traceable.h" + +struct _SysprofDocumentAllocation +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentAllocationClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_ADDRESS, + PROP_IS_FREE, + PROP_SIZE, + PROP_STACK_DEPTH, + PROP_THREAD_ID, + N_PROPS +}; + +static guint +sysprof_document_allocation_get_stack_depth (SysprofDocumentTraceable *traceable) +{ + const SysprofCaptureAllocation *allocation = SYSPROF_DOCUMENT_FRAME_GET (traceable, SysprofCaptureAllocation); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (traceable, allocation->n_addrs); +} + +static guint64 +sysprof_document_allocation_get_stack_address (SysprofDocumentTraceable *traceable, + guint position) +{ + const SysprofCaptureAllocation *allocation = SYSPROF_DOCUMENT_FRAME_GET (traceable, SysprofCaptureAllocation); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (traceable, allocation->addrs[position]); +} + +static guint +sysprof_document_allocation_get_stack_addresses (SysprofDocumentTraceable *traceable, + guint64 *addresses, + guint n_addresses) +{ + const SysprofCaptureAllocation *allocation = SYSPROF_DOCUMENT_FRAME_GET (traceable, SysprofCaptureAllocation); + guint depth = MIN (n_addresses, SYSPROF_DOCUMENT_FRAME_UINT16 (traceable, allocation->n_addrs)); + + for (guint i = 0; i < depth; i++) + addresses[i] = SYSPROF_DOCUMENT_FRAME_UINT64 (traceable, allocation->addrs[i]); + + return depth; +} + +static int +sysprof_document_allocation_get_thread_id (SysprofDocumentTraceable *traceable) +{ + SysprofDocumentAllocation *self = (SysprofDocumentAllocation *)traceable; + const SysprofCaptureAllocation *allocation; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_ALLOCATION (self), 0); + + allocation = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureAllocation); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, allocation->tid); +} + +static void +traceable_iface_init (SysprofDocumentTraceableInterface *iface) +{ + iface->get_stack_depth = sysprof_document_allocation_get_stack_depth; + iface->get_stack_address = sysprof_document_allocation_get_stack_address; + iface->get_stack_addresses = sysprof_document_allocation_get_stack_addresses; + iface->get_thread_id = sysprof_document_allocation_get_thread_id; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocumentAllocation, sysprof_document_allocation, SYSPROF_TYPE_DOCUMENT_FRAME, + G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_DOCUMENT_TRACEABLE, traceable_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_allocation_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentAllocation *self = SYSPROF_DOCUMENT_ALLOCATION (object); + + switch (prop_id) + { + case PROP_ADDRESS: + g_value_set_uint64 (value, sysprof_document_allocation_get_address (self)); + break; + + case PROP_IS_FREE: + g_value_set_boolean (value, sysprof_document_allocation_is_free (self)); + break; + + case PROP_SIZE: + g_value_set_int64 (value, sysprof_document_allocation_get_size (self)); + break; + + case PROP_STACK_DEPTH: + g_value_set_uint (value, sysprof_document_traceable_get_stack_depth (SYSPROF_DOCUMENT_TRACEABLE (self))); + break; + + case PROP_THREAD_ID: + g_value_set_int (value, sysprof_document_traceable_get_thread_id (SYSPROF_DOCUMENT_TRACEABLE (self))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_allocation_class_init (SysprofDocumentAllocationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofDocumentFrameClass *document_frame_class = SYSPROF_DOCUMENT_FRAME_CLASS (klass); + + object_class->get_property = sysprof_document_allocation_get_property; + + document_frame_class->type_name = N_("Allocation"); + + /** + * SysprofDocumentAllocation:thread-id: + * + * The thread-id where the stack was traced. + * + * On Linux, this is generally set to the value of `gettid()`. + * + * Since: 45 + */ + properties [PROP_THREAD_ID] = + g_param_spec_int ("thread-id", NULL, NULL, + -1, G_MAXINT32, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * SysprofDocumentAllocation:address: + * + * The address that was allocated or freed. + * + * Since: 45 + */ + properties [PROP_ADDRESS] = + g_param_spec_uint64 ("address", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * SysprofDocumentAllocation:size: + * + * The size of the memory that was allocated or freed. + * + * Since: 45 + */ + properties [PROP_SIZE] = + g_param_spec_int64 ("size", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * SysprofDocumentAllocation:is-free: + * + * If this allocation record is a call to release + * #SysprofDocumentAllocation:address. + * + * Since: 45 + */ + properties [PROP_IS_FREE] = + g_param_spec_boolean ("is-free", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * SysprofDocumentAllocation:stack-depth: + * + * The depth of the stack trace. + * + * Since: 45 + */ + properties [PROP_STACK_DEPTH] = + g_param_spec_uint ("stack-depth", NULL, NULL, + 0, G_MAXUINT16, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_allocation_init (SysprofDocumentAllocation *self) +{ +} + +guint64 +sysprof_document_allocation_get_address (SysprofDocumentAllocation *self) +{ + const SysprofCaptureAllocation *allocation; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_ALLOCATION (self), 0); + + allocation = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureAllocation); + + return SYSPROF_DOCUMENT_FRAME_UINT64 (self, allocation->alloc_addr); +} + +gint64 +sysprof_document_allocation_get_size (SysprofDocumentAllocation *self) +{ + const SysprofCaptureAllocation *allocation; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_ALLOCATION (self), 0); + + allocation = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureAllocation); + + return SYSPROF_DOCUMENT_FRAME_INT64 (self, allocation->alloc_size); +} + +gboolean +sysprof_document_allocation_is_free (SysprofDocumentAllocation *self) +{ + const SysprofCaptureAllocation *allocation; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_ALLOCATION (self), 0); + + allocation = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureAllocation); + + return allocation->alloc_size == 0; +} diff --git a/src/libsysprof/sysprof-document-allocation.h b/src/libsysprof/sysprof-document-allocation.h new file mode 100644 index 00000000..9744bf6b --- /dev/null +++ b/src/libsysprof/sysprof-document-allocation.h @@ -0,0 +1,48 @@ +/* sysprof-document-allocation.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_ALLOCATION (sysprof_document_allocation_get_type()) +#define SYSPROF_IS_DOCUMENT_ALLOCATION(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_ALLOCATION) +#define SYSPROF_DOCUMENT_ALLOCATION(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_ALLOCATION, SysprofDocumentAllocation) +#define SYSPROF_DOCUMENT_ALLOCATION_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_ALLOCATION, SysprofDocumentAllocationClass) + +typedef struct _SysprofDocumentAllocation SysprofDocumentAllocation; +typedef struct _SysprofDocumentAllocationClass SysprofDocumentAllocationClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_allocation_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +guint64 sysprof_document_allocation_get_address (SysprofDocumentAllocation *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_allocation_get_size (SysprofDocumentAllocation *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_allocation_get_tid (SysprofDocumentAllocation *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_document_allocation_is_free (SysprofDocumentAllocation *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentAllocation, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-bitset-index-private.h b/src/libsysprof/sysprof-document-bitset-index-private.h new file mode 100644 index 00000000..0432f441 --- /dev/null +++ b/src/libsysprof/sysprof-document-bitset-index-private.h @@ -0,0 +1,35 @@ +/* sysprof-document-bitset-index.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_BITSET_INDEX (sysprof_document_bitset_index_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofDocumentBitsetIndex, sysprof_document_bitset_index, SYSPROF, DOCUMENT_BITSET_INDEX, GObject) + +GListModel *_sysprof_document_bitset_index_new (GListModel *model, + EggBitset *bitset); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-bitset-index.c b/src/libsysprof/sysprof-document-bitset-index.c new file mode 100644 index 00000000..112cb6e6 --- /dev/null +++ b/src/libsysprof/sysprof-document-bitset-index.c @@ -0,0 +1,154 @@ +/* sysprof-document-bitset-index.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-bitset-index-private.h" + +struct _SysprofDocumentBitsetIndex +{ + GObject parent_instance; + GListModel *model; + EggBitset *bitset; +}; + +static GType +sysprof_document_bitset_index_get_item_type (GListModel *model) +{ + SysprofDocumentBitsetIndex *self = SYSPROF_DOCUMENT_BITSET_INDEX (model); + + if (self->model != NULL) + return g_list_model_get_item_type (self->model); + + return G_TYPE_OBJECT; +} + +static guint +sysprof_document_bitset_index_get_n_items (GListModel *model) +{ + SysprofDocumentBitsetIndex *self = SYSPROF_DOCUMENT_BITSET_INDEX (model); + + if (self->bitset != NULL) + return egg_bitset_get_size (self->bitset); + + return 0; +} + +static gpointer +sysprof_document_bitset_index_get_item (GListModel *model, + guint position) +{ + SysprofDocumentBitsetIndex *self = SYSPROF_DOCUMENT_BITSET_INDEX (model); + + if (self->model == NULL || self->bitset == NULL) + return NULL; + + if (position >= egg_bitset_get_size (self->bitset)) + return NULL; + + return g_list_model_get_item (self->model, + egg_bitset_get_nth (self->bitset, position)); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item = sysprof_document_bitset_index_get_item; + iface->get_item_type = sysprof_document_bitset_index_get_item_type; + iface->get_n_items = sysprof_document_bitset_index_get_n_items; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocumentBitsetIndex, sysprof_document_bitset_index, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +enum { + PROP_0, + PROP_N_ITEMS, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static void +sysprof_document_bitset_index_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentBitsetIndex *self = SYSPROF_DOCUMENT_BITSET_INDEX (object); + + switch (prop_id) + { + case PROP_N_ITEMS: + g_value_set_uint (value, g_list_model_get_n_items (G_LIST_MODEL (self))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_bitset_index_dispose (GObject *object) +{ + SysprofDocumentBitsetIndex *self = (SysprofDocumentBitsetIndex *)object; + + g_clear_pointer (&self->bitset, egg_bitset_unref); + g_clear_object (&self->model); + + G_OBJECT_CLASS (sysprof_document_bitset_index_parent_class)->dispose (object); +} + +static void +sysprof_document_bitset_index_class_init (SysprofDocumentBitsetIndexClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_document_bitset_index_dispose; + object_class->get_property = sysprof_document_bitset_index_get_property; + + properties[PROP_N_ITEMS] = + g_param_spec_uint ("n-items", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_bitset_index_init (SysprofDocumentBitsetIndex *self) +{ +} + +GListModel * +_sysprof_document_bitset_index_new (GListModel *model, + EggBitset *bitset) +{ + SysprofDocumentBitsetIndex *self; + + g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL); + g_return_val_if_fail (bitset != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_DOCUMENT_BITSET_INDEX, NULL); + self->model = g_object_ref (model); + self->bitset = egg_bitset_ref (bitset); + + return G_LIST_MODEL (self); +} diff --git a/src/libsysprof/sysprof-document-counter-private.h b/src/libsysprof/sysprof-document-counter-private.h new file mode 100644 index 00000000..9995221f --- /dev/null +++ b/src/libsysprof/sysprof-document-counter-private.h @@ -0,0 +1,50 @@ +/* sysprof-document-counter-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-counter.h" + +G_BEGIN_DECLS + +struct _SysprofDocumentCounter +{ + GObject parent_instance; + GRefString *category; + GRefString *description; + GRefString *name; + GArray *values; + double min_value; + double max_value; + gint64 begin_time; + guint id; + guint type; +}; + +SysprofDocumentCounter *_sysprof_document_counter_new (guint id, + guint type, + GRefString *category, + GRefString *name, + GRefString *description, + GArray *values, + gint64 begin_time); +void _sysprof_document_counter_calculate_range (SysprofDocumentCounter *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-counter-value-private.h b/src/libsysprof/sysprof-document-counter-value-private.h new file mode 100644 index 00000000..7a238588 --- /dev/null +++ b/src/libsysprof/sysprof-document-counter-value-private.h @@ -0,0 +1,32 @@ +/* sysprof-document-counter-value-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-counter-value.h" +#include "sysprof-document-private.h" + +G_BEGIN_DECLS + +SysprofDocumentCounterValue *_sysprof_document_counter_value_new (guint type, + const SysprofDocumentTimedValue *value, + SysprofDocumentCounter *counter); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-counter-value.c b/src/libsysprof/sysprof-document-counter-value.c new file mode 100644 index 00000000..9287010e --- /dev/null +++ b/src/libsysprof/sysprof-document-counter-value.c @@ -0,0 +1,220 @@ +/* sysprof-document-counter-value.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-counter-private.h" +#include "sysprof-document-counter-value-private.h" + +struct _SysprofDocumentCounterValue +{ + GObject parent_instance; + SysprofDocumentCounter *counter; + SysprofDocumentTimedValue value; + guint type; +}; + +enum { + PROP_0, + PROP_COUNTER, + PROP_TIME, + PROP_TIME_OFFSET, + PROP_VALUE_DOUBLE, + PROP_VALUE_INT64, + PROP_VALUE_STRING, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentCounterValue, sysprof_document_counter_value, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_counter_value_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentCounterValue *self = SYSPROF_DOCUMENT_COUNTER_VALUE (object); + + switch (prop_id) + { + case PROP_COUNTER: + g_value_set_object (value, sysprof_document_counter_value_get_counter (self)); + break; + + case PROP_TIME: + g_value_set_int64 (value, sysprof_document_counter_value_get_time (self)); + break; + + case PROP_TIME_OFFSET: + g_value_set_int64 (value, sysprof_document_counter_value_get_time_offset (self)); + break; + + case PROP_VALUE_DOUBLE: + g_value_set_double (value, sysprof_document_counter_value_get_value_double (self)); + break; + + case PROP_VALUE_INT64: + g_value_set_int64 (value, sysprof_document_counter_value_get_value_int64 (self)); + break; + + case PROP_VALUE_STRING: + g_value_take_string (value, sysprof_document_counter_value_format (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_counter_value_class_init (SysprofDocumentCounterValueClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_counter_value_get_property; + + properties [PROP_COUNTER] = + g_param_spec_object ("counter", NULL, NULL, + SYSPROF_TYPE_DOCUMENT_COUNTER, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TIME] = + g_param_spec_int64 ("time", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TIME_OFFSET] = + g_param_spec_int64 ("time-offset", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_VALUE_DOUBLE] = + g_param_spec_double ("value-double", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_VALUE_INT64] = + g_param_spec_int64 ("value-int64", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_VALUE_STRING] = + g_param_spec_string ("value-string", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_counter_value_init (SysprofDocumentCounterValue *self) +{ +} + +SysprofDocumentCounterValue * +_sysprof_document_counter_value_new (guint type, + const SysprofDocumentTimedValue *value, + SysprofDocumentCounter *counter) +{ + SysprofDocumentCounterValue *self; + + self = g_object_new (SYSPROF_TYPE_DOCUMENT_COUNTER_VALUE, NULL); + self->type = type; + self->value = *value; + self->counter = g_object_ref (counter); + + return self; +} + +gint64 +sysprof_document_counter_value_get_time (SysprofDocumentCounterValue *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (self), 0); + + return self->value.time; +} + +gint64 +sysprof_document_counter_value_get_time_offset (SysprofDocumentCounterValue *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (self), 0); + + return self->value.time - self->counter->begin_time; +} + +void +sysprof_document_counter_value_get_value (SysprofDocumentCounterValue *self, + GValue *value) +{ + g_return_if_fail (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (self)); + g_return_if_fail (G_IS_VALUE (value)); + + if (G_VALUE_HOLDS_INT64 (value)) + g_value_set_int64 (value, self->value.v_int64); + else if (G_VALUE_HOLDS_DOUBLE (value)) + g_value_set_double (value, self->value.v_double); + else + g_warning_once ("Unsupported value type %s", G_VALUE_TYPE_NAME (value)); +} + +gint64 +sysprof_document_counter_value_get_value_int64 (SysprofDocumentCounterValue *self) +{ + if (self->type == SYSPROF_CAPTURE_COUNTER_INT64) + return self->value.v_int64; + else + return (gint64)self->value.v_double; +} + +double +sysprof_document_counter_value_get_value_double (SysprofDocumentCounterValue *self) +{ + if (self->type == SYSPROF_CAPTURE_COUNTER_DOUBLE) + return self->value.v_double; + else + return (double)self->value.v_int64; +} + +char * +sysprof_document_counter_value_format (SysprofDocumentCounterValue *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (self), NULL); + + if (self->type == SYSPROF_CAPTURE_COUNTER_DOUBLE) + return g_strdup_printf ("%lf", self->value.v_double); + else + return g_strdup_printf ("%ld", self->value.v_int64); +} + +/** + * sysprof_document_counter_value_get_counter: + * @self: a #SysprofDocumentCounterValue + * + * Returns: (transfer none): a #SysprofDocumentCounter + */ +SysprofDocumentCounter * +sysprof_document_counter_value_get_counter (SysprofDocumentCounterValue *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (self), NULL); + + return self->counter; +} diff --git a/src/libsysprof/sysprof-document-counter-value.h b/src/libsysprof/sysprof-document-counter-value.h new file mode 100644 index 00000000..99c65cd2 --- /dev/null +++ b/src/libsysprof/sysprof-document-counter-value.h @@ -0,0 +1,53 @@ +/* sysprof-document-counter-value.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-document-counter.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_COUNTER_VALUE (sysprof_document_counter_value_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofDocumentCounterValue, sysprof_document_counter_value, SYSPROF, DOCUMENT_COUNTER_VALUE, GObject) + +SYSPROF_AVAILABLE_IN_ALL +SysprofDocumentCounter *sysprof_document_counter_value_get_counter (SysprofDocumentCounterValue *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_counter_value_get_time (SysprofDocumentCounterValue *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_counter_value_get_time_offset (SysprofDocumentCounterValue *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_counter_value_get_value (SysprofDocumentCounterValue *self, + GValue *value); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_counter_value_get_value_int64 (SysprofDocumentCounterValue *self); +SYSPROF_AVAILABLE_IN_ALL +double sysprof_document_counter_value_get_value_double (SysprofDocumentCounterValue *self); +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_document_counter_value_format (SysprofDocumentCounterValue *self); + + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-counter.c b/src/libsysprof/sysprof-document-counter.c new file mode 100644 index 00000000..6331cf22 --- /dev/null +++ b/src/libsysprof/sysprof-document-counter.c @@ -0,0 +1,412 @@ +/* sysprof-document-counter.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-counter-private.h" +#include "sysprof-document-counter-value-private.h" +#include "sysprof-document-private.h" + +enum { + PROP_0, + PROP_CATEGORY, + PROP_DESCRIPTION, + PROP_ID, + PROP_KEY, + PROP_MAX_VALUE, + PROP_MIN_VALUE, + PROP_NAME, + N_PROPS +}; + +static guint +sysprof_document_counter_get_n_items (GListModel *model) +{ + return sysprof_document_counter_get_n_values (SYSPROF_DOCUMENT_COUNTER (model)); +} + +static GType +sysprof_document_counter_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_DOCUMENT_COUNTER_VALUE; +} + +static gpointer +sysprof_document_counter_get_item (GListModel *model, + guint position) +{ + SysprofDocumentCounter *self = SYSPROF_DOCUMENT_COUNTER (model); + const SysprofDocumentTimedValue *value; + + if (position >= self->values->len) + return NULL; + + value = &g_array_index (self->values, SysprofDocumentTimedValue, position); + + return _sysprof_document_counter_value_new (self->type, value, self); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_n_items = sysprof_document_counter_get_n_items; + iface->get_item_type = sysprof_document_counter_get_item_type; + iface->get_item = sysprof_document_counter_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocumentCounter, sysprof_document_counter, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_counter_finalize (GObject *object) +{ + SysprofDocumentCounter *self = (SysprofDocumentCounter *)object; + + g_clear_pointer (&self->category, g_ref_string_release); + g_clear_pointer (&self->description, g_ref_string_release); + g_clear_pointer (&self->name, g_ref_string_release); + g_clear_pointer (&self->values, g_array_unref); + + G_OBJECT_CLASS (sysprof_document_counter_parent_class)->finalize (object); +} + +static void +sysprof_document_counter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentCounter *self = SYSPROF_DOCUMENT_COUNTER (object); + + switch (prop_id) + { + case PROP_CATEGORY: + g_value_set_string (value, sysprof_document_counter_get_category (self)); + break; + + case PROP_DESCRIPTION: + g_value_set_string (value, sysprof_document_counter_get_description (self)); + break; + + case PROP_MIN_VALUE: + g_value_set_double (value, self->min_value); + break; + + case PROP_MAX_VALUE: + g_value_set_double (value, self->max_value); + break; + + case PROP_NAME: + g_value_set_string (value, sysprof_document_counter_get_name (self)); + break; + + case PROP_KEY: + g_value_take_string (value, sysprof_document_counter_dup_key (self)); + break; + + case PROP_ID: + g_value_set_uint (value, sysprof_document_counter_get_id (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_counter_class_init (SysprofDocumentCounterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_counter_finalize; + object_class->get_property = sysprof_document_counter_get_property; + + properties [PROP_ID] = + g_param_spec_uint ("id", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_CATEGORY] = + g_param_spec_string ("category", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DESCRIPTION] = + g_param_spec_string ("description", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_KEY] = + g_param_spec_string ("key", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_NAME] = + g_param_spec_string ("name", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MIN_VALUE] = + g_param_spec_double ("min-value", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MAX_VALUE] = + g_param_spec_double ("max-value", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_counter_init (SysprofDocumentCounter *self) +{ +} + +SysprofDocumentCounter * +_sysprof_document_counter_new (guint id, + guint type, + GRefString *category, + GRefString *name, + GRefString *description, + GArray *values, + gint64 begin_time) +{ + SysprofDocumentCounter *self; + + self = g_object_new (SYSPROF_TYPE_DOCUMENT_COUNTER, NULL); + self->id = id; + self->type = type; + self->category = category; + self->name = name; + self->description = description; + self->values = values; + self->begin_time = begin_time; + + return self; +} + +const char * +sysprof_document_counter_get_category (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), NULL); + + return self->category; +} + +const char * +sysprof_document_counter_get_description (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), NULL); + + if (self->description == NULL || self->description[0] == 0) + return NULL; + + return self->description; +} + +const char * +sysprof_document_counter_get_name (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), NULL); + + return self->name; +} + +guint +sysprof_document_counter_get_id (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), 0); + + return self->id; +} + +GType +sysprof_document_counter_get_value_type (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), G_TYPE_INVALID); + + if (self->type == SYSPROF_CAPTURE_COUNTER_INT64) + return G_TYPE_INT64; + + if (self->type == SYSPROF_CAPTURE_COUNTER_DOUBLE) + return G_TYPE_DOUBLE; + + g_return_val_if_reached (G_TYPE_INVALID); +} + +guint +sysprof_document_counter_get_n_values (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), 0); + + return self->values->len; +} + +void +sysprof_document_counter_get_value (SysprofDocumentCounter *self, + guint nth, + gint64 *time, + GValue *value) +{ + g_return_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self)); + g_return_if_fail (nth < self->values->len); + g_return_if_fail (value == NULL || G_IS_VALUE (value)); + + if (time != NULL) + *time = g_array_index (self->values, SysprofDocumentTimedValue, nth).time; + + if (value == NULL) + return; + + if (G_VALUE_HOLDS_INT64 (value)) + g_value_set_int64 (value, g_array_index (self->values, SysprofDocumentTimedValue, nth).v_int64); + else if (G_VALUE_HOLDS_DOUBLE (value)) + g_value_set_double (value, g_array_index (self->values, SysprofDocumentTimedValue, nth).v_double); +} + +gint64 +sysprof_document_counter_get_value_int64 (SysprofDocumentCounter *self, + guint nth, + gint64 *time) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), 0); + g_return_val_if_fail (nth < self->values->len, 0); + + if (time != NULL) + *time = g_array_index (self->values, SysprofDocumentTimedValue, nth).time; + + return g_array_index (self->values, SysprofDocumentTimedValue, nth).v_int64; +} + +double +sysprof_document_counter_get_value_double (SysprofDocumentCounter *self, + guint nth, + gint64 *time) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), 0); + g_return_val_if_fail (nth < self->values->len, 0); + + if (time != NULL) + *time = g_array_index (self->values, SysprofDocumentTimedValue, nth).time; + + return g_array_index (self->values, SysprofDocumentTimedValue, nth).v_double; +} + +static inline double +value_as_double (guint type, + const SysprofDocumentTimedValue *value) +{ + if (type == SYSPROF_CAPTURE_COUNTER_DOUBLE) + return value->v_double; + else if (type == SYSPROF_CAPTURE_COUNTER_INT64) + return value->v_int64; + else + return .0; +} + +static int +sort_by_time (gconstpointer aptr, + gconstpointer bptr) +{ + const SysprofDocumentTimedValue *a = aptr; + const SysprofDocumentTimedValue *b = bptr; + + if (a->time < b->time) + return -1; + + if (a->time > b->time) + return 1; + + return 0; +} + +void +_sysprof_document_counter_calculate_range (SysprofDocumentCounter *self) +{ + const SysprofDocumentTimedValue *values; + gboolean min_value_changed = FALSE; + gboolean max_value_changed = FALSE; + double min_value; + double max_value; + guint n_values; + + g_return_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self)); + + if (self->values->len == 0) + return; + + g_array_sort (self->values, sort_by_time); + + values = &g_array_index (self->values, SysprofDocumentTimedValue, 0); + n_values = self->values->len; + + min_value = value_as_double (self->type, &values[0]); + max_value = min_value; + + for (guint i = 1; i < n_values; i++) + { + double value = value_as_double (self->type, &values[i]); + + min_value = MIN (min_value, value); + max_value = MAX (max_value, value); + } + + min_value_changed = self->min_value != min_value; + max_value_changed = self->max_value != max_value; + + self->min_value = min_value; + self->max_value = max_value; + + if (min_value_changed) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_VALUE]); + + if (max_value_changed) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_VALUE]); +} + +double +sysprof_document_counter_get_max_value (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), .0); + + return self->max_value; +} + +double +sysprof_document_counter_get_min_value (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), .0); + + return self->min_value; +} + +char * +sysprof_document_counter_dup_key (SysprofDocumentCounter *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_COUNTER (self), NULL); + + return g_strdup_printf ("%s/%s", self->category, self->name); +} diff --git a/src/libsysprof/sysprof-document-counter.h b/src/libsysprof/sysprof-document-counter.h new file mode 100644 index 00000000..fec9b3ac --- /dev/null +++ b/src/libsysprof/sysprof-document-counter.h @@ -0,0 +1,66 @@ +/* sysprof-document-counter.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_COUNTER (sysprof_document_counter_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofDocumentCounter, sysprof_document_counter, SYSPROF, DOCUMENT_COUNTER, GObject) + +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_document_counter_dup_key (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_counter_get_category (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_counter_get_description (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_counter_get_name (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_counter_get_id (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_counter_get_value_type (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_counter_get_n_values (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_counter_get_value (SysprofDocumentCounter *self, + guint nth, + gint64 *time, + GValue *value); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_counter_get_value_int64 (SysprofDocumentCounter *self, + guint nth, + gint64 *time); +SYSPROF_AVAILABLE_IN_ALL +double sysprof_document_counter_get_value_double (SysprofDocumentCounter *self, + guint nth, + gint64 *time); +SYSPROF_AVAILABLE_IN_ALL +double sysprof_document_counter_get_max_value (SysprofDocumentCounter *self); +SYSPROF_AVAILABLE_IN_ALL +double sysprof_document_counter_get_min_value (SysprofDocumentCounter *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-ctrdef.c b/src/libsysprof/sysprof-document-ctrdef.c new file mode 100644 index 00000000..a6bba826 --- /dev/null +++ b/src/libsysprof/sysprof-document-ctrdef.c @@ -0,0 +1,105 @@ +/* sysprof-document-ctrdef.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-ctrdef.h" + +struct _SysprofDocumentCtrdef +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentCtrdefClass +{ + SysprofDocumentFrameClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentCtrdef, sysprof_document_ctrdef, SYSPROF_TYPE_DOCUMENT_FRAME) + +static void +sysprof_document_ctrdef_class_init (SysprofDocumentCtrdefClass *klass) +{ +} + +static void +sysprof_document_ctrdef_init (SysprofDocumentCtrdef *self) +{ +} + +guint +sysprof_document_ctrdef_get_n_counters (SysprofDocumentCtrdef *self) +{ + const SysprofCaptureCounterDefine *ctrdef; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_CTRDEF (self), 0); + + ctrdef = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureCounterDefine); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (self, ctrdef->n_counters); +} + +/** + * sysprof_document_ctrdef_get_counter: + * @self: a #SysprofDocumentCtrdef + * @nth: a value between 0 and sysprof_document_ctrdef_get_n_counters() + * @id: (out): a location for the id of the counter + * @type: (out): a location for either %SYSPROF_CAPTURE_COUNTER_INT64 + * or %SYSPROF_CAPTURE_COUNTER_DOUBLE + * @category: (out): a location for the category + * @name: (out): a location for the name + * @description: (out): a location for the description + * + * Gets information about a counter defined in @self. + */ +void +sysprof_document_ctrdef_get_counter (SysprofDocumentCtrdef *self, + guint nth, + guint *id, + guint *type, + const char **category, + const char **name, + const char **description) +{ + const SysprofCaptureCounterDefine *ctrdef; + const SysprofCaptureCounter *ctr; + + g_return_if_fail (SYSPROF_IS_DOCUMENT_CTRDEF (self)); + g_return_if_fail (nth < sysprof_document_ctrdef_get_n_counters (self)); + + ctrdef = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureCounterDefine); + ctr = &ctrdef->counters[nth]; + + if (id != NULL) + *id = SYSPROF_DOCUMENT_FRAME_UINT32 (self, ctr->id); + + if (type != NULL) + *type = ctr->type; + + if (category != NULL) + *category = SYSPROF_DOCUMENT_FRAME_CSTRING (self, ctr->category); + + if (name != NULL) + *name = SYSPROF_DOCUMENT_FRAME_CSTRING (self, ctr->name); + + if (description != NULL) + *description = SYSPROF_DOCUMENT_FRAME_CSTRING (self, ctr->description); +} diff --git a/src/libsysprof/sysprof-document-ctrdef.h b/src/libsysprof/sysprof-document-ctrdef.h new file mode 100644 index 00000000..c8e1a652 --- /dev/null +++ b/src/libsysprof/sysprof-document-ctrdef.h @@ -0,0 +1,53 @@ +/* sysprof-document-ctrdef.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_CTRDEF (sysprof_document_ctrdef_get_type()) +#define SYSPROF_IS_DOCUMENT_CTRDEF(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_CTRDEF) +#define SYSPROF_DOCUMENT_CTRDEF(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_CTRDEF, SysprofDocumentCtrdef) +#define SYSPROF_DOCUMENT_CTRDEF_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_CTRDEF, SysprofDocumentCtrdefClass) + +typedef struct _SysprofDocumentCtrdef SysprofDocumentCtrdef; +typedef struct _SysprofDocumentCtrdefClass SysprofDocumentCtrdefClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_ctrdef_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_ctrdef_get_n_counters (SysprofDocumentCtrdef *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_ctrdef_get_counter (SysprofDocumentCtrdef *self, + guint nth, + guint *id, + guint *type, + const char **category, + const char **name, + const char **description); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentCtrdef, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-ctrset.c b/src/libsysprof/sysprof-document-ctrset.c new file mode 100644 index 00000000..0ad120a2 --- /dev/null +++ b/src/libsysprof/sysprof-document-ctrset.c @@ -0,0 +1,116 @@ +/* sysprof-document-ctrset.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-ctrset.h" + +struct _SysprofDocumentCtrset +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentCtrsetClass +{ + SysprofDocumentFrameClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentCtrset, sysprof_document_ctrset, SYSPROF_TYPE_DOCUMENT_FRAME) + +static void +sysprof_document_ctrset_class_init (SysprofDocumentCtrsetClass *klass) +{ +} + +static void +sysprof_document_ctrset_init (SysprofDocumentCtrset *self) +{ +} + +guint +sysprof_document_ctrset_get_n_values (SysprofDocumentCtrset *self) +{ + const SysprofCaptureCounterSet *ctrset; + gconstpointer endptr; + guint n_groups; + guint n_values = 0; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_CTRSET (self), 0); + + endptr = SYSPROF_DOCUMENT_FRAME_ENDPTR (self); + ctrset = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureCounterSet); + + n_groups = SYSPROF_DOCUMENT_FRAME_UINT16 (self, ctrset->n_values); + + for (guint i = 0; i < n_groups; i++) + { + const SysprofCaptureCounterValues *values = &ctrset->values[i]; + + /* Don't allow overflowing the frame zone */ + if ((gconstpointer)&values[1] > endptr) + break; + + for (guint j = 0; j < G_N_ELEMENTS (values->ids); j++) + { + if (values->ids[j] == 0) + break; + n_values++; + } + } + + return n_values; +} + +/** + * sysprof_document_ctrset_get_raw_value: (skip) + * @self: a #SysprofDocumentCtrset + * @nth: the nth value to get + * @id: (out): a location for the counter id + * @value: a location to store the raw value + * + * The raw value is 8 bytes and may not be converted to local + * byte ordering. + * + * @nth must be less-than sysprof_document_ctrset_get_n_values(). + */ +void +sysprof_document_ctrset_get_raw_value (SysprofDocumentCtrset *self, + guint nth, + guint *id, + guint8 value[restrict 8]) +{ + const SysprofCaptureCounterSet *ctrset; + guint group; + guint pos; + + g_return_if_fail (SYSPROF_IS_DOCUMENT_CTRSET (self)); + g_return_if_fail (nth < sysprof_document_ctrset_get_n_values (self)); + g_return_if_fail (value != NULL); + + group = nth / 8; + pos = nth % 8; + + ctrset = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureCounterSet); + + *id = SYSPROF_DOCUMENT_FRAME_UINT32 (self, ctrset->values[group].ids[pos]); + + memcpy (value, &ctrset->values[group].values[pos], 8); +} diff --git a/src/libsysprof/sysprof-document-ctrset.h b/src/libsysprof/sysprof-document-ctrset.h new file mode 100644 index 00000000..3bc6cc32 --- /dev/null +++ b/src/libsysprof/sysprof-document-ctrset.h @@ -0,0 +1,50 @@ +/* sysprof-document-ctrset.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_CTRSET (sysprof_document_ctrset_get_type()) +#define SYSPROF_IS_DOCUMENT_CTRSET(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_CTRSET) +#define SYSPROF_DOCUMENT_CTRSET(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_CTRSET, SysprofDocumentCtrset) +#define SYSPROF_DOCUMENT_CTRSET_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_CTRSET, SysprofDocumentCtrsetClass) + +typedef struct _SysprofDocumentCtrset SysprofDocumentCtrset; +typedef struct _SysprofDocumentCtrsetClass SysprofDocumentCtrsetClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_ctrset_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_ctrset_get_n_values (SysprofDocumentCtrset *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_ctrset_get_raw_value (SysprofDocumentCtrset *self, + guint nth, + guint *id, + guint8 value[restrict 8]); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentCtrset, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof-ui/sysprof-cell-renderer-duration.h b/src/libsysprof/sysprof-document-exit.c similarity index 55% rename from src/libsysprof-ui/sysprof-cell-renderer-duration.h rename to src/libsysprof/sysprof-document-exit.c index 243a261c..4e83cbab 100644 --- a/src/libsysprof-ui/sysprof-cell-renderer-duration.h +++ b/src/libsysprof/sysprof-document-exit.c @@ -1,6 +1,6 @@ -/* sysprof-cell-renderer-duration.h +/* sysprof-document-exit.c * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,24 +18,29 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#pragma once +#include "config.h" -#include +#include "sysprof-document-frame-private.h" +#include "sysprof-document-exit.h" -G_BEGIN_DECLS - -#define SYSPROF_TYPE_CELL_RENDERER_DURATION (sysprof_cell_renderer_duration_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofCellRendererDuration, sysprof_cell_renderer_duration, SYSPROF, CELL_RENDERER_DURATION, GtkCellRenderer) - -struct _SysprofCellRendererDurationClass +struct _SysprofDocumentExit { - GtkCellRendererClass parent_class; - - /*< private >*/ - gpointer _reserved[8]; + SysprofDocumentFrame parent_instance; }; -GtkCellRenderer *sysprof_cell_renderer_duration_new (void); +struct _SysprofDocumentExitClass +{ + SysprofDocumentFrameClass parent_class; +}; -G_END_DECLS +G_DEFINE_FINAL_TYPE (SysprofDocumentExit, sysprof_document_exit, SYSPROF_TYPE_DOCUMENT_FRAME) + +static void +sysprof_document_exit_class_init (SysprofDocumentExitClass *klass) +{ +} + +static void +sysprof_document_exit_init (SysprofDocumentExit *self) +{ +} diff --git a/src/libsysprof/sysprof-document-exit.h b/src/libsysprof/sysprof-document-exit.h new file mode 100644 index 00000000..b9f351eb --- /dev/null +++ b/src/libsysprof/sysprof-document-exit.h @@ -0,0 +1,41 @@ +/* sysprof-document-exit.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_EXIT (sysprof_document_exit_get_type()) +#define SYSPROF_IS_DOCUMENT_EXIT(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_EXIT) +#define SYSPROF_DOCUMENT_EXIT(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_EXIT, SysprofDocumentExit) +#define SYSPROF_DOCUMENT_EXIT_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_EXIT, SysprofDocumentExitClass) + +typedef struct _SysprofDocumentExit SysprofDocumentExit; +typedef struct _SysprofDocumentExitClass SysprofDocumentExitClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_exit_get_type (void) G_GNUC_CONST; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentExit, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-file-chunk.c b/src/libsysprof/sysprof-document-file-chunk.c new file mode 100644 index 00000000..97e621f9 --- /dev/null +++ b/src/libsysprof/sysprof-document-file-chunk.c @@ -0,0 +1,156 @@ +/* sysprof-document-file-chunk.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" + +#include "sysprof-document-file-chunk.h" + +struct _SysprofDocumentFileChunk +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentFileChunkClass +{ + SysprofDocumentFrameClass parent_instance; +}; + +enum { + PROP_0, + PROP_IS_LAST, + PROP_SIZE, + PROP_PATH, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentFileChunk, sysprof_document_file_chunk, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_file_chunk_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentFileChunk *self = SYSPROF_DOCUMENT_FILE_CHUNK (object); + + switch (prop_id) + { + case PROP_IS_LAST: + g_value_set_boolean (value, sysprof_document_file_chunk_get_is_last (self)); + break; + + case PROP_SIZE: + g_value_set_uint (value, sysprof_document_file_chunk_get_size (self)); + break; + + case PROP_PATH: + g_value_set_string (value, sysprof_document_file_chunk_get_path (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_file_chunk_class_init (SysprofDocumentFileChunkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_file_chunk_get_property; + + properties [PROP_IS_LAST] = + g_param_spec_boolean ("is-last", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SIZE] = + g_param_spec_uint ("size", NULL, NULL, + 0, G_MAXUINT16, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_PATH] = + g_param_spec_string ("path", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_file_chunk_init (SysprofDocumentFileChunk *self) +{ +} + +gboolean +sysprof_document_file_chunk_get_is_last (SysprofDocumentFileChunk *self) +{ + const SysprofCaptureFileChunk *file_chunk; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE_CHUNK (self), FALSE); + + file_chunk = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureFileChunk); + + return file_chunk->is_last; +} + +guint +sysprof_document_file_chunk_get_size (SysprofDocumentFileChunk *self) +{ + const SysprofCaptureFileChunk *file_chunk; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE_CHUNK (self), FALSE); + + file_chunk = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureFileChunk); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (self, file_chunk->len); +} + +const char * +sysprof_document_file_chunk_get_path (SysprofDocumentFileChunk *self) +{ + const SysprofCaptureFileChunk *file_chunk; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE_CHUNK (self), FALSE); + + file_chunk = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureFileChunk); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, file_chunk->path); +} + +const guint8 * +sysprof_document_file_chunk_get_data (SysprofDocumentFileChunk *self, + guint *size) +{ + const SysprofCaptureFileChunk *file_chunk; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE_CHUNK (self), FALSE); + + file_chunk = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureFileChunk); + + if (size != NULL) + *size = sysprof_document_file_chunk_get_size (self); + + return file_chunk->data; +} diff --git a/src/libsysprof/sysprof-document-file-chunk.h b/src/libsysprof/sysprof-document-file-chunk.h new file mode 100644 index 00000000..caf7dd1c --- /dev/null +++ b/src/libsysprof/sysprof-document-file-chunk.h @@ -0,0 +1,49 @@ +/* sysprof-document-file-chunk.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_FILE_CHUNK (sysprof_document_file_chunk_get_type()) +#define SYSPROF_IS_DOCUMENT_FILE_CHUNK(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_FILE_CHUNK) +#define SYSPROF_DOCUMENT_FILE_CHUNK(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_FILE_CHUNK, SysprofDocumentFileChunk) +#define SYSPROF_DOCUMENT_FILE_CHUNK_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_FILE_CHUNK, SysprofDocumentFileChunkClass) + +typedef struct _SysprofDocumentFileChunk SysprofDocumentFileChunk; +typedef struct _SysprofDocumentFileChunkClass SysprofDocumentFileChunkClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_file_chunk_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_document_file_chunk_get_is_last (SysprofDocumentFileChunk *self); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_file_chunk_get_size (SysprofDocumentFileChunk *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_file_chunk_get_path (SysprofDocumentFileChunk *self); +SYSPROF_AVAILABLE_IN_ALL +const guint8 *sysprof_document_file_chunk_get_data (SysprofDocumentFileChunk *self, + guint *size); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentFileChunk, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-file-private.h b/src/libsysprof/sysprof-document-file-private.h new file mode 100644 index 00000000..4f61b926 --- /dev/null +++ b/src/libsysprof/sysprof-document-file-private.h @@ -0,0 +1,31 @@ +/* sysprof-document-file-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-file.h" + +G_BEGIN_DECLS + +SysprofDocumentFile *_sysprof_document_file_new (const char *path, + GPtrArray *file_chunks, + gboolean compressed); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-file.c b/src/libsysprof/sysprof-document-file.c new file mode 100644 index 00000000..fe949e34 --- /dev/null +++ b/src/libsysprof/sysprof-document-file.c @@ -0,0 +1,279 @@ +/* sysprof-document-file.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-file-chunk.h" +#include "sysprof-document-file-private.h" +#include "sysprof-document-frame-private.h" + +struct _SysprofDocumentFile +{ + GObject parent_instance; + char *path; + GPtrArray *file_chunks; + guint compressed : 1; +}; + +enum { + PROP_0, + PROP_BYTES, + PROP_IS_COMPRESSED, + PROP_PATH, + PROP_SIZE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentFile, sysprof_document_file, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_file_finalize (GObject *object) +{ + SysprofDocumentFile *self = (SysprofDocumentFile *)object; + + g_clear_pointer (&self->path, g_free); + g_clear_pointer (&self->file_chunks, g_ptr_array_unref); + + G_OBJECT_CLASS (sysprof_document_file_parent_class)->finalize (object); +} + +static void +sysprof_document_file_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentFile *self = SYSPROF_DOCUMENT_FILE (object); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_string (value, sysprof_document_file_get_path (self)); + break; + + case PROP_BYTES: + g_value_take_boxed (value, sysprof_document_file_dup_bytes (self)); + break; + + case PROP_IS_COMPRESSED: + g_value_set_boolean (value, sysprof_document_file_is_compressed (self)); + break; + + case PROP_SIZE: + g_value_set_uint64 (value, sysprof_document_file_get_size (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_file_class_init (SysprofDocumentFileClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_file_finalize; + object_class->get_property = sysprof_document_file_get_property; + + properties [PROP_PATH] = + g_param_spec_string ("path", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BYTES] = + g_param_spec_boxed ("bytes", NULL, NULL, + G_TYPE_BYTES, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_IS_COMPRESSED] = + g_param_spec_boolean ("is-compressed", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SIZE] = + g_param_spec_uint64 ("size", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_file_init (SysprofDocumentFile *self) +{ +} + +SysprofDocumentFile * +_sysprof_document_file_new (const char *path, + GPtrArray *file_chunks, + gboolean compressed) +{ + SysprofDocumentFile *self; + + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (file_chunks != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_DOCUMENT_FILE, NULL); + self->path = g_strdup (path); + self->file_chunks = file_chunks; + self->compressed = !!compressed; + + return self; +} + +const char * +sysprof_document_file_get_path (SysprofDocumentFile *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE (self), NULL); + + return self->path; +} + +/** + * sysprof_document_file_dup_contents: + * @self: a #SysprofDocumentFile + * + * Creates a new #GBytes containing the contents of the file. + * + * Returns: (transfer full): a #GBytes + */ +GBytes * +sysprof_document_file_dup_bytes (SysprofDocumentFile *self) +{ + GArray *ar; + guint len; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE (self), NULL); + + ar = g_array_new (TRUE, FALSE, sizeof (char)); + + for (guint i = 0; i < self->file_chunks->len; i++) + { + SysprofDocumentFileChunk *file_chunk = g_ptr_array_index (self->file_chunks, i); + const guint8 *data = sysprof_document_file_chunk_get_data (file_chunk, &len); + + g_array_append_vals (ar, data, len); + } + + len = ar->len; + + if (self->compressed) + { + guint8 *data = (guint8 *)g_array_free (ar, FALSE); + g_autoptr(GInputStream) input = g_memory_input_stream_new_from_data (data, len, g_free); + g_autoptr(GOutputStream) memory_output = g_memory_output_stream_new_resizable (); + g_autoptr(GZlibDecompressor) zlib = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); + g_autoptr(GOutputStream) zlib_output = g_converter_output_stream_new (memory_output, G_CONVERTER (zlib)); + + g_output_stream_splice (zlib_output, + input, + (G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET), + NULL, NULL); + + return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (memory_output)); + } + + return g_bytes_new_take (g_array_free (ar, FALSE), len); +} + +/** + * sysprof_document_file_read: + * @self: a #SysprofDocumentFile + * + * Creates a new input stream containing the contents of the file + * within the document. + * + * Returns: (transfer full): a #GInputstream + */ +GInputStream * +sysprof_document_file_read (SysprofDocumentFile *self) +{ + g_autoptr(GInputStream) input = NULL; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE (self), NULL); + + input = g_memory_input_stream_new (); + + for (guint i = 0; i < self->file_chunks->len; i++) + { + g_autoptr(GBytes) bytes = NULL; + SysprofDocumentFileChunk *file_chunk; + const guint8 *data; + guint len; + + file_chunk = g_ptr_array_index (self->file_chunks, i); + data = sysprof_document_file_chunk_get_data (file_chunk, &len); + + bytes = g_bytes_new_with_free_func (data, + len, + (GDestroyNotify)g_mapped_file_unref, + g_mapped_file_ref (SYSPROF_DOCUMENT_FRAME (file_chunk)->mapped_file)); + + g_memory_input_stream_add_bytes (G_MEMORY_INPUT_STREAM (input), bytes); + } + + if (self->compressed) + { + g_autoptr(GZlibDecompressor) zlib = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); + + return g_converter_input_stream_new (input, G_CONVERTER (zlib)); + } + + return g_steal_pointer (&input); +} + +gsize +sysprof_document_file_get_size (SysprofDocumentFile *self) +{ + gsize size = 0; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE (self), 0); + + for (guint i = 0; i < self->file_chunks->len; i++) + { + SysprofDocumentFileChunk *file_chunk = g_ptr_array_index (self->file_chunks, i); + + size += sysprof_document_file_chunk_get_size (file_chunk); + } + + return size; +} + +/** + * sysprof_document_file_is_compressed: + * @self: a #SysprofDocumentFile + * + * Checks if the embedded file is compressed. If so, %TRUE is returned. + * + * Note that files are transparently decompressed when opening streams. + * + * Returns: %TRUE if the embedded file was compressed + */ +gboolean +sysprof_document_file_is_compressed (SysprofDocumentFile *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FILE (self), FALSE); + + return self->compressed; +} diff --git a/src/libsysprof/sysprof-tracefd-source.h b/src/libsysprof/sysprof-document-file.h similarity index 53% rename from src/libsysprof/sysprof-tracefd-source.h rename to src/libsysprof/sysprof-document-file.h index 1dfd5519..faa60747 100644 --- a/src/libsysprof/sysprof-tracefd-source.h +++ b/src/libsysprof/sysprof-document-file.h @@ -1,6 +1,6 @@ -/* sysprof-tracefd-source.h +/* sysprof-document-file.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,30 +20,26 @@ #pragma once -#include "sysprof-source.h" +#include + +#include G_BEGIN_DECLS -#define SYSPROF_TYPE_TRACEFD_SOURCE (sysprof_tracefd_source_get_type()) +#define SYSPROF_TYPE_DOCUMENT_FILE (sysprof_document_file_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofTracefdSource, sysprof_tracefd_source, SYSPROF, TRACEFD_SOURCE, GObject) - -struct _SysprofTracefdSourceClass -{ - GObjectClass parent_class; - - /*< private >*/ - gpointer _reserved[16]; -}; +G_DECLARE_FINAL_TYPE (SysprofDocumentFile, sysprof_document_file, SYSPROF, DOCUMENT_FILE, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_tracefd_source_new (void); +const char *sysprof_document_file_get_path (SysprofDocumentFile *self); SYSPROF_AVAILABLE_IN_ALL -const gchar *sysprof_tracefd_source_get_envvar (SysprofTracefdSource *self); +GBytes *sysprof_document_file_dup_bytes (SysprofDocumentFile *self); SYSPROF_AVAILABLE_IN_ALL -void sysprof_tracefd_source_set_envvar (SysprofTracefdSource *self, - const gchar *envvar); - +GInputStream *sysprof_document_file_read (SysprofDocumentFile *self); +SYSPROF_AVAILABLE_IN_ALL +gsize sysprof_document_file_get_size (SysprofDocumentFile *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_document_file_is_compressed (SysprofDocumentFile *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-document-fork.c b/src/libsysprof/sysprof-document-fork.c new file mode 100644 index 00000000..fd4c44bf --- /dev/null +++ b/src/libsysprof/sysprof-document-fork.c @@ -0,0 +1,95 @@ +/* sysprof-document-fork.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-fork.h" + +struct _SysprofDocumentFork +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentForkClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_CHILD_PID, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentFork, sysprof_document_fork, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_fork_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentFork *self = SYSPROF_DOCUMENT_FORK (object); + + switch (prop_id) + { + case PROP_CHILD_PID: + g_value_set_int (value, sysprof_document_fork_get_child_pid (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_fork_class_init (SysprofDocumentForkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_fork_get_property; + + properties [PROP_CHILD_PID] = + g_param_spec_int ("child-pid", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_fork_init (SysprofDocumentFork *self) +{ +} + +int +sysprof_document_fork_get_child_pid (SysprofDocumentFork *self) +{ + const SysprofCaptureFork *fork; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FORK (self), 0); + + fork = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureFork); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, fork->child_pid); +} diff --git a/src/libsysprof/sysprof-document-fork.h b/src/libsysprof/sysprof-document-fork.h new file mode 100644 index 00000000..cd8caef0 --- /dev/null +++ b/src/libsysprof/sysprof-document-fork.h @@ -0,0 +1,43 @@ +/* sysprof-document-fork.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_FORK (sysprof_document_fork_get_type()) +#define SYSPROF_IS_DOCUMENT_FORK(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_FORK) +#define SYSPROF_DOCUMENT_FORK(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_FORK, SysprofDocumentFork) +#define SYSPROF_DOCUMENT_FORK_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_FORK, SysprofDocumentForkClass) + +typedef struct _SysprofDocumentFork SysprofDocumentFork; +typedef struct _SysprofDocumentForkClass SysprofDocumentForkClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_fork_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_fork_get_child_pid (SysprofDocumentFork *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentFork, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-frame-private.h b/src/libsysprof/sysprof-document-frame-private.h new file mode 100644 index 00000000..b743b549 --- /dev/null +++ b/src/libsysprof/sysprof-document-frame-private.h @@ -0,0 +1,112 @@ +/* sysprof-document-frame-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +struct _SysprofDocumentFrame +{ + GObject parent; + GMappedFile *mapped_file; + const SysprofCaptureFrame *frame; + gint64 time_offset; + guint32 frame_len : 16; + guint32 needs_swap : 1; + guint32 padding : 15; +}; + +struct _SysprofDocumentFrameClass +{ + GObjectClass parent_class; + const char *type_name; + char *(*dup_tooltip) (SysprofDocumentFrame *self); +}; + +SysprofDocumentFrame *_sysprof_document_frame_new (GMappedFile *mapped, + const SysprofCaptureFrame *frame, + guint16 frame_len, + gboolean needs_swap, + gint64 begin_time, + gint64 end_time); + +#define SYSPROF_DOCUMENT_FRAME_GET_CLASS(obj) \ + G_TYPE_INSTANCE_GET_CLASS(obj, SYSPROF_TYPE_DOCUMENT_FRAME, SysprofDocumentFrameClass) + +#define SYSPROF_DOCUMENT_FRAME_ENDPTR(obj) \ + (&((const guint8 *)SYSPROF_DOCUMENT_FRAME(obj)->frame)[SYSPROF_DOCUMENT_FRAME(obj)->frame_len]) + +#define SYSPROF_DOCUMENT_FRAME_GET(obj, type) \ + ((const type *)(SYSPROF_DOCUMENT_FRAME(obj)->frame)) + +#define SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) \ + (SYSPROF_DOCUMENT_FRAME (obj)->needs_swap) + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +# define SYSPROF_DOCUMENT_FRAME_UINT16(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GUINT16_TO_LE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_INT16(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GINT16_TO_LE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_UINT32(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GUINT32_TO_LE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_INT32(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GINT32_TO_LE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_UINT64(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GUINT64_TO_LE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_INT64(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GINT64_TO_LE(val) : (val)) +#else +# define SYSPROF_DOCUMENT_FRAME_UINT16(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GUINT16_TO_BE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_INT16(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GINT16_TO_BE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_UINT32(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GUINT32_TO_BE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_INT32(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GINT32_TO_BE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_UINT64(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GUINT64_TO_BE(val) : (val)) +# define SYSPROF_DOCUMENT_FRAME_INT64(obj, val) \ + (SYSPROF_DOCUMENT_FRAME_NEEDS_SWAP(obj) ? GINT64_TO_BE(val) : (val)) +#endif + +static inline const char * +_SYSPROF_DOCUMENT_FRAME_CSTRING (SysprofDocumentFrame *self, + const char *str) +{ + const char *endptr = (const char *)self->frame + self->frame_len; + + for (const char *c = str; c < endptr; c++) + { + if (*c == 0) + return str; + } + + return NULL; +} + +#define SYSPROF_DOCUMENT_FRAME_CSTRING(obj,str) \ + _SYSPROF_DOCUMENT_FRAME_CSTRING(SYSPROF_DOCUMENT_FRAME(obj),str) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-frame.c b/src/libsysprof/sysprof-document-frame.c new file mode 100644 index 00000000..c191a35c --- /dev/null +++ b/src/libsysprof/sysprof-document-frame.c @@ -0,0 +1,363 @@ +/* sysprof-document-frame.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-frame-private.h" + +#include "sysprof-document-allocation.h" +#include "sysprof-document-ctrdef.h" +#include "sysprof-document-ctrset.h" +#include "sysprof-document-exit.h" +#include "sysprof-document-file-chunk.h" +#include "sysprof-document-fork.h" +#include "sysprof-document-log.h" +#include "sysprof-document-jitmap.h" +#include "sysprof-document-mark.h" +#include "sysprof-document-metadata.h" +#include "sysprof-document-mmap.h" +#include "sysprof-document-overlay.h" +#include "sysprof-document-process.h" +#include "sysprof-document-sample.h" + +G_DEFINE_TYPE (SysprofDocumentFrame, sysprof_document_frame, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_CPU, + PROP_PID, + PROP_TIME, + PROP_TIME_OFFSET, + PROP_TIME_STRING, + PROP_TOOLTIP, + PROP_TYPE_NAME, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static char * +sysprof_document_frame_real_dup_tooltip (SysprofDocumentFrame *self) +{ + g_autofree char *time_string = sysprof_document_frame_dup_time_string (self); + + return g_strdup_printf ("%s: %s", + time_string, + SYSPROF_DOCUMENT_FRAME_GET_CLASS (self)->type_name); +} + +static const char * +sysprof_document_frame_get_type_name (SysprofDocumentFrame *self) +{ + return g_dgettext (GETTEXT_PACKAGE, SYSPROF_DOCUMENT_FRAME_GET_CLASS (self)->type_name); +} + +static void +sysprof_document_frame_finalize (GObject *object) +{ + SysprofDocumentFrame *self = (SysprofDocumentFrame *)object; + + g_clear_pointer (&self->mapped_file, g_mapped_file_unref); + + self->frame = NULL; + self->needs_swap = 0; + + G_OBJECT_CLASS (sysprof_document_frame_parent_class)->finalize (object); +} + +static void +sysprof_document_frame_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentFrame *self = SYSPROF_DOCUMENT_FRAME (object); + + switch (prop_id) + { + case PROP_CPU: + g_value_set_int (value, sysprof_document_frame_get_cpu (self)); + break; + + case PROP_PID: + g_value_set_int (value, sysprof_document_frame_get_pid (self)); + break; + + case PROP_TIME: + g_value_set_int64 (value, sysprof_document_frame_get_time (self)); + break; + + case PROP_TIME_OFFSET: + g_value_set_int64 (value, sysprof_document_frame_get_time_offset (self)); + break; + + case PROP_TIME_STRING: + g_value_take_string (value, sysprof_document_frame_dup_time_string (self)); + break; + + case PROP_TOOLTIP: + g_value_take_string (value, sysprof_document_frame_dup_tooltip (self)); + break; + + case PROP_TYPE_NAME: + g_value_set_static_string (value, sysprof_document_frame_get_type_name (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_frame_class_init (SysprofDocumentFrameClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_frame_finalize; + object_class->get_property = sysprof_document_frame_get_property; + + klass->type_name = N_("Frame"); + klass->dup_tooltip = sysprof_document_frame_real_dup_tooltip; + + properties[PROP_CPU] = + g_param_spec_int ("cpu", NULL, NULL, + G_MININT32, + G_MAXINT32, + -1, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_PID] = + g_param_spec_int ("pid", NULL, NULL, + G_MININT32, + G_MAXINT32, + -1, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TIME] = + g_param_spec_int64 ("time", NULL, NULL, + G_MININT64, + G_MAXINT64, + 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TIME_OFFSET] = + g_param_spec_int64 ("time-offset", NULL, NULL, + G_MININT64, + G_MAXINT64, + 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TIME_STRING] = + g_param_spec_string ("time-string", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TYPE_NAME] = + g_param_spec_string ("type-name", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TOOLTIP] = + g_param_spec_string ("tooltip", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_frame_init (SysprofDocumentFrame *self) +{ +} + +SysprofDocumentFrame * +_sysprof_document_frame_new (GMappedFile *mapped_file, + const SysprofCaptureFrame *frame, + guint16 frame_len, + gboolean needs_swap, + gint64 begin_time, + gint64 end_time) +{ + SysprofDocumentFrame *self; + GType gtype; + + switch (frame->type) + { + case SYSPROF_CAPTURE_FRAME_SAMPLE: + gtype = SYSPROF_TYPE_DOCUMENT_SAMPLE; + break; + + case SYSPROF_CAPTURE_FRAME_MAP: + gtype = SYSPROF_TYPE_DOCUMENT_MMAP; + break; + + case SYSPROF_CAPTURE_FRAME_LOG: + gtype = SYSPROF_TYPE_DOCUMENT_LOG; + break; + + case SYSPROF_CAPTURE_FRAME_MARK: + gtype = SYSPROF_TYPE_DOCUMENT_MARK; + break; + + case SYSPROF_CAPTURE_FRAME_METADATA: + gtype = SYSPROF_TYPE_DOCUMENT_METADATA; + break; + + case SYSPROF_CAPTURE_FRAME_PROCESS: + gtype = SYSPROF_TYPE_DOCUMENT_PROCESS; + break; + + case SYSPROF_CAPTURE_FRAME_EXIT: + gtype = SYSPROF_TYPE_DOCUMENT_EXIT; + break; + + case SYSPROF_CAPTURE_FRAME_FORK: + gtype = SYSPROF_TYPE_DOCUMENT_FORK; + break; + + case SYSPROF_CAPTURE_FRAME_ALLOCATION: + gtype = SYSPROF_TYPE_DOCUMENT_ALLOCATION; + break; + + case SYSPROF_CAPTURE_FRAME_FILE_CHUNK: + gtype = SYSPROF_TYPE_DOCUMENT_FILE_CHUNK; + break; + + case SYSPROF_CAPTURE_FRAME_OVERLAY: + gtype = SYSPROF_TYPE_DOCUMENT_OVERLAY; + break; + + case SYSPROF_CAPTURE_FRAME_JITMAP: + gtype = SYSPROF_TYPE_DOCUMENT_JITMAP; + break; + + case SYSPROF_CAPTURE_FRAME_CTRDEF: + gtype = SYSPROF_TYPE_DOCUMENT_CTRDEF; + break; + + case SYSPROF_CAPTURE_FRAME_CTRSET: + gtype = SYSPROF_TYPE_DOCUMENT_CTRSET; + break; + + default: + gtype = SYSPROF_TYPE_DOCUMENT_FRAME; + break; + } + + + self = g_object_new (gtype, NULL); + self->mapped_file = g_mapped_file_ref (mapped_file); + self->frame = frame; + self->frame_len = frame_len; + self->needs_swap = !!needs_swap; + + self->time_offset = CLAMP (sysprof_document_frame_get_time (self) - begin_time, 0, G_MAXINT64); + + return self; +} + +int +sysprof_document_frame_get_cpu (SysprofDocumentFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FRAME (self), 0); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, self->frame->cpu); +} + +int +sysprof_document_frame_get_pid (SysprofDocumentFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FRAME (self), 0); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, self->frame->pid); +} + +gint64 +sysprof_document_frame_get_time (SysprofDocumentFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FRAME (self), 0); + + return SYSPROF_DOCUMENT_FRAME_INT64 (self, self->frame->time); +} + +gint64 +sysprof_document_frame_get_time_offset (SysprofDocumentFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FRAME (self), 0); + + return self->time_offset; +} + +gboolean +sysprof_document_frame_equal (const SysprofDocumentFrame *a, + const SysprofDocumentFrame *b) +{ + return a->frame == b->frame; +} + +/** + * sysprof_document_frame_dup_time_string: + * @self: a #SysprofDocumentFrame + * + * Gets the time formatted as a string such as `00:00:00.1234`. + * + * Returns: (transfer full): a new string + */ +char * +sysprof_document_frame_dup_time_string (SysprofDocumentFrame *self) +{ + int hours; + int minutes; + int seconds; + double time; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FRAME (self), NULL); + + time = self->time_offset / (double)SYSPROF_NSEC_PER_SEC; + + hours = time / (60 * 60); + time -= hours * (60 * 60); + + minutes = time / 60; + time -= minutes * 60; + + seconds = time / SYSPROF_NSEC_PER_SEC; + time -= seconds * SYSPROF_NSEC_PER_SEC; + + return g_strdup_printf ("%02d:%02d:%02d.%04d", hours, minutes, seconds, (int)(time * 10000)); +} + +/** + * sysprof_document_frame_dup_tooltip: + * @self: a #SysprofDocumentFrame + * + * Returns a new string containing suggested tooltip text. + * + * Returns: (transfer full): a string + */ +char * +sysprof_document_frame_dup_tooltip (SysprofDocumentFrame *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_FRAME (self), NULL); + + return SYSPROF_DOCUMENT_FRAME_GET_CLASS (self)->dup_tooltip (self); +} diff --git a/src/libsysprof/sysprof-document-frame.h b/src/libsysprof/sysprof-document-frame.h new file mode 100644 index 00000000..57662960 --- /dev/null +++ b/src/libsysprof/sysprof-document-frame.h @@ -0,0 +1,57 @@ +/* sysprof-document-frame.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_FRAME (sysprof_document_frame_get_type()) +#define SYSPROF_IS_DOCUMENT_FRAME(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_FRAME) +#define SYSPROF_DOCUMENT_FRAME(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_FRAME, SysprofDocumentFrame) +#define SYSPROF_DOCUMENT_FRAME_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_FRAME, SysprofDocumentFrameClass) + +typedef struct _SysprofDocumentFrame SysprofDocumentFrame; +typedef struct _SysprofDocumentFrameClass SysprofDocumentFrameClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_frame_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_frame_get_cpu (SysprofDocumentFrame *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_frame_get_pid (SysprofDocumentFrame *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_frame_get_time (SysprofDocumentFrame *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_frame_get_time_offset (SysprofDocumentFrame *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_document_frame_equal (const SysprofDocumentFrame *a, + const SysprofDocumentFrame *b); +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_document_frame_dup_tooltip (SysprofDocumentFrame *self); +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_document_frame_dup_time_string (SysprofDocumentFrame *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentFrame, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-jitmap.c b/src/libsysprof/sysprof-document-jitmap.c new file mode 100644 index 00000000..aa4f8e05 --- /dev/null +++ b/src/libsysprof/sysprof-document-jitmap.c @@ -0,0 +1,169 @@ +/* sysprof-document-jitmap.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-jitmap.h" + +typedef struct _Jitmap +{ + SysprofAddress address; + const char *name; +} Jitmap; + +struct _SysprofDocumentJitmap +{ + SysprofDocumentFrame parent_instance; + GArray *jitmaps; + guint initialized : 1; +}; + +struct _SysprofDocumentJitmapClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_SIZE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentJitmap, sysprof_document_jitmap, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_jitmap_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentJitmap *self = SYSPROF_DOCUMENT_JITMAP (object); + + switch (prop_id) + { + case PROP_SIZE: + g_value_set_uint (value, sysprof_document_jitmap_get_size (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_jitmap_class_init (SysprofDocumentJitmapClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_jitmap_get_property; + + properties [PROP_SIZE] = + g_param_spec_string ("size", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_jitmap_init (SysprofDocumentJitmap *self) +{ + self->jitmaps = g_array_new (FALSE, FALSE, sizeof (Jitmap)); +} + +guint +sysprof_document_jitmap_get_size (SysprofDocumentJitmap *self) +{ + const SysprofCaptureJitmap *jitmap; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_JITMAP (self), 0); + + jitmap = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureJitmap); + + return SYSPROF_DOCUMENT_FRAME_UINT32 (self, jitmap->n_jitmaps); +} + +/** + * sysprof_document_jitmap_get_mapping: + * @self: a #SysprofDocumentJitmap + * @nth: the index of the mapping + * @address: (out): a location for the address + * + * Gets the @nth mapping and returns it as @address and the return value + * of this function. + * + * Returns: (nullable): the name of the symbol, or %NULL if @nth is + * out of bounds. + */ +const char * +sysprof_document_jitmap_get_mapping (SysprofDocumentJitmap *self, + guint nth, + SysprofAddress *address) +{ + const SysprofCaptureJitmap *frame; + const Jitmap *j; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_JITMAP (self), NULL); + g_return_val_if_fail (address != NULL, NULL); + + if G_UNLIKELY (!self->initialized) + { + const guint8 *pos; + const guint8 *endptr; + + self->initialized = TRUE; + + frame = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureJitmap); + endptr = SYSPROF_DOCUMENT_FRAME_ENDPTR (self); + pos = frame->data; + + while (pos < endptr) + { + SysprofAddress addr; + Jitmap map; + + if (pos + sizeof addr >= endptr) + break; + + memcpy (&addr, pos, sizeof addr); + pos += sizeof addr; + + map.address = SYSPROF_DOCUMENT_FRAME_UINT64 (self, addr); + + if (!(map.name = SYSPROF_DOCUMENT_FRAME_CSTRING (self, (const char *)pos))) + break; + + pos += strlen (map.name) + 1; + + g_array_append_val (self->jitmaps, map); + } + } + + if (nth >= self->jitmaps->len) + return NULL; + + j = &g_array_index (self->jitmaps, Jitmap, nth); + *address = j->address; + return j->name; +} + diff --git a/src/libsysprof/sysprof-document-jitmap.h b/src/libsysprof/sysprof-document-jitmap.h new file mode 100644 index 00000000..f6b83543 --- /dev/null +++ b/src/libsysprof/sysprof-document-jitmap.h @@ -0,0 +1,49 @@ +/* sysprof-document-jitmap.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_JITMAP (sysprof_document_jitmap_get_type()) +#define SYSPROF_IS_DOCUMENT_JITMAP(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_JITMAP) +#define SYSPROF_DOCUMENT_JITMAP(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_JITMAP, SysprofDocumentJitmap) +#define SYSPROF_DOCUMENT_JITMAP_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_JITMAP, SysprofDocumentJitmapClass) + +typedef struct _SysprofDocumentJitmap SysprofDocumentJitmap; +typedef struct _SysprofDocumentJitmapClass SysprofDocumentJitmapClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_jitmap_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_jitmap_get_size (SysprofDocumentJitmap *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_jitmap_get_mapping (SysprofDocumentJitmap *self, + guint position, + SysprofAddress *address); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentJitmap, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-loader.c b/src/libsysprof/sysprof-document-loader.c new file mode 100644 index 00000000..6622f325 --- /dev/null +++ b/src/libsysprof/sysprof-document-loader.c @@ -0,0 +1,659 @@ +/* sysprof-document-loader.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "sysprof-bundled-symbolizer.h" +#include "sysprof-document-bitset-index-private.h" +#include "sysprof-document-loader.h" +#include "sysprof-document-private.h" +#include "sysprof-elf-symbolizer.h" +#include "sysprof-jitmap-symbolizer.h" +#include "sysprof-kallsyms-symbolizer.h" +#include "sysprof-multi-symbolizer.h" + +struct _SysprofDocumentLoader +{ + GObject parent_instance; + GMutex mutex; + SysprofSymbolizer *symbolizer; + char *filename; + char *message; + double fraction; + int fd; + guint notify_source; + guint symbolizing : 1; +}; + +enum { + PROP_0, + PROP_FRACTION, + PROP_MESSAGE, + PROP_SYMBOLIZER, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentLoader, sysprof_document_loader, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static gboolean +progress_notify_in_idle (gpointer data) +{ + SysprofDocumentLoader *self = data; + + g_assert (SYSPROF_IS_DOCUMENT_LOADER (self)); + + g_mutex_lock (&self->mutex); + self->notify_source = 0; + g_mutex_unlock (&self->mutex); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FRACTION]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MESSAGE]); + + return G_SOURCE_REMOVE; +} + +static void +set_progress (double fraction, + const char *message, + gpointer user_data) +{ + SysprofDocumentLoader *self = user_data; + + g_assert (SYSPROF_IS_DOCUMENT_LOADER (self)); + + g_mutex_lock (&self->mutex); + + self->fraction = fraction * .5; + + if (self->symbolizing) + self->fraction += .5; + + g_set_str (&self->message, message); + + if (!self->notify_source) + self->notify_source = g_idle_add_full (G_PRIORITY_LOW, + progress_notify_in_idle, + g_object_ref (self), + g_object_unref); + g_mutex_unlock (&self->mutex); +} + +static void +mapped_file_by_filename (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GMappedFile) mapped_file = g_mapped_file_new (task_data, FALSE, &error); + + if (mapped_file != NULL) + g_task_return_pointer (task, + g_steal_pointer (&mapped_file), + (GDestroyNotify)g_mapped_file_unref); + else + g_task_return_error (task, g_steal_pointer (&error)); +} + +static void +mapped_file_new_async (const char *filename, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_task_data (task, g_strdup (filename), g_free); + g_task_run_in_thread (task, mapped_file_by_filename); +} + +static void +mapped_file_by_fd (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GMappedFile) mapped_file = g_mapped_file_new_from_fd (GPOINTER_TO_INT (task_data), FALSE, &error); + + if (mapped_file != NULL) + g_task_return_pointer (task, + g_steal_pointer (&mapped_file), + (GDestroyNotify)g_mapped_file_unref); + else + g_task_return_error (task, g_steal_pointer (&error)); +} + +static void +close_fd (gpointer data) +{ + int fd = GPOINTER_TO_INT (data); + close (fd); +} + +static void +mapped_file_new_from_fd_async (int fd, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + int copy_fd; + + task = g_task_new (NULL, cancellable, callback, user_data); + + if (-1 == (copy_fd = dup (fd))) + { + int errsv = errno; + g_task_return_new_error (task, + G_IO_ERROR, + g_io_error_from_errno (errsv), + "%s", + g_strerror (errsv)); + return; + } + + g_task_set_task_data (task, GINT_TO_POINTER (copy_fd), close_fd); + g_task_run_in_thread (task, mapped_file_by_fd); +} + +static GMappedFile * +mapped_file_new_finish (GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +set_default_symbolizer (SysprofDocumentLoader *self) +{ + g_autoptr(SysprofMultiSymbolizer) multi = NULL; + + g_assert (SYSPROF_IS_DOCUMENT_LOADER (self)); + + g_clear_object (&self->symbolizer); + + multi = sysprof_multi_symbolizer_new (); + sysprof_multi_symbolizer_take (multi, sysprof_bundled_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_elf_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); + self->symbolizer = SYSPROF_SYMBOLIZER (g_steal_pointer (&multi)); +} + +static void +sysprof_document_loader_finalize (GObject *object) +{ + SysprofDocumentLoader *self = (SysprofDocumentLoader *)object; + + g_clear_handle_id (&self->notify_source, g_source_remove); + g_clear_object (&self->symbolizer); + g_clear_pointer (&self->filename, g_free); + g_clear_pointer (&self->message, g_free); + g_clear_fd (&self->fd, NULL); + g_mutex_clear (&self->mutex); + + G_OBJECT_CLASS (sysprof_document_loader_parent_class)->finalize (object); +} + +static void +sysprof_document_loader_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentLoader *self = SYSPROF_DOCUMENT_LOADER (object); + + switch (prop_id) + { + case PROP_FRACTION: + g_value_set_double (value, sysprof_document_loader_get_fraction (self)); + break; + + case PROP_MESSAGE: + g_value_set_string (value, sysprof_document_loader_get_message (self)); + break; + + case PROP_SYMBOLIZER: + g_value_set_object (value, sysprof_document_loader_get_symbolizer (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_loader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentLoader *self = SYSPROF_DOCUMENT_LOADER (object); + + switch (prop_id) + { + case PROP_SYMBOLIZER: + sysprof_document_loader_set_symbolizer (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_loader_class_init (SysprofDocumentLoaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_loader_finalize; + object_class->get_property = sysprof_document_loader_get_property; + object_class->set_property = sysprof_document_loader_set_property; + + properties [PROP_FRACTION] = + g_param_spec_double ("fraction", NULL, NULL, + 0, 1, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MESSAGE] = + g_param_spec_string ("message", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SYMBOLIZER] = + g_param_spec_object ("symbolizer", NULL, NULL, + SYSPROF_TYPE_SYMBOLIZER, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + g_type_ensure (SYSPROF_TYPE_DOCUMENT); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_BITSET_INDEX); +} + +static void +sysprof_document_loader_init (SysprofDocumentLoader *self) +{ + g_mutex_init (&self->mutex); + + self->fd = -1; + + set_default_symbolizer (self); +} + +SysprofDocumentLoader * +sysprof_document_loader_new (const char *filename) +{ + SysprofDocumentLoader *self; + + g_return_val_if_fail (filename != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_DOCUMENT_LOADER, NULL); + self->filename = g_strdup (filename); + + return self; +} + +SysprofDocumentLoader * +sysprof_document_loader_new_for_fd (int fd, + GError **error) +{ + g_autoptr(SysprofDocumentLoader) self = NULL; + + self = g_object_new (SYSPROF_TYPE_DOCUMENT_LOADER, NULL); + + if (-1 == (self->fd = dup (fd))) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + return NULL; + } + + return g_steal_pointer (&self); +} + +/** + * sysprof_document_loader_get_symbolizer: + * @self: a #SysprofDocumentLoader + * + * Gets the #SysprofSymbolizer to use to symbolize traces within + * the document. + * + * Returns: (transfer none) (nullable): a #SysprofSymbolizer or %NULL + */ +SysprofSymbolizer * +sysprof_document_loader_get_symbolizer (SysprofDocumentLoader *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self), NULL); + + return self->symbolizer; +} + +/** + * sysprof_document_loader_set_symbolizer: + * @self: a #SysprofDocumentLoader + * @symbolizer: (nullable): a #SysprofSymbolizer or %NULL + * + * Sets the symbolizer to use to convert instruction pointers to + * symbol names in the document. + * + * If set to %NULL, a sensible default will be chosen when loading. + */ +void +sysprof_document_loader_set_symbolizer (SysprofDocumentLoader *self, + SysprofSymbolizer *symbolizer) +{ + g_return_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self)); + + if (g_set_object (&self->symbolizer, symbolizer)) + { + if (self->symbolizer == NULL) + set_default_symbolizer (self); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SYMBOLIZER]); + } +} + +/** + * sysprof_document_loader_get_fraction: + * @self: a #SysprofDocumentLoader + * + * Gets the fraction between 0 and 1 for the loading that has occurred. + * + * Returns: A value between 0 and 1. + */ +double +sysprof_document_loader_get_fraction (SysprofDocumentLoader *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self), .0); + + return self->fraction; +} + +/** + * sysprof_document_loader_get_message: + * @self: a #SysprofDocumentLoader + * + * Gets a text message representing what is happenin with loading. + * + * This only updates between calls of sysprof_document_loader_load_async() + * and sysprof_document_loader_load_finish(). + * + * Returns: (nullable): a string containing a load message + */ +const char * +sysprof_document_loader_get_message (SysprofDocumentLoader *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self), NULL); + + return self->message; +} + +static void +sysprof_document_loader_load_symbols_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocument *document = (SysprofDocument *)object; + g_autoptr(GError) error = NULL; + g_autoptr(GTask) task = user_data; + SysprofDocumentLoader *self; + + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + self = g_task_get_source_object (task); + + set_progress (0., _("Document loaded"), self); + + if (!_sysprof_document_symbolize_finish (document, result, &error)) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_pointer (task, g_object_ref (document), g_object_unref); +} + +static void +sysprof_document_loader_load_document_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GTask) task = user_data; + SysprofDocumentLoader *self; + SysprofSymbolizer *symbolizer; + + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + self = g_task_get_source_object (task); + symbolizer = g_task_get_task_data (task); + + g_assert (symbolizer != NULL); + g_assert (SYSPROF_IS_SYMBOLIZER (symbolizer)); + + if (!(document = _sysprof_document_new_finish (result, &error))) + { + g_task_return_error (task, g_steal_pointer (&error)); + set_progress (1., _("Loading failed"), self); + return; + } + + if (self->filename != NULL) + { + g_autofree char *title = g_path_get_basename (self->filename); + _sysprof_document_set_title (document, title); + } + + self->symbolizing = TRUE; + + set_progress (.0, _("Symbolizing stack traces"), self); + + _sysprof_document_symbolize_async (document, + symbolizer, + set_progress, + g_object_ref (self), + g_object_unref, + g_task_get_cancellable (task), + sysprof_document_loader_load_symbols_cb, + g_object_ref (task)); +} + +static void +sysprof_document_loader_load_mapped_file_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GMappedFile) mapped_file = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GTask) task = user_data; + SysprofDocumentLoader *self; + + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + self = g_task_get_source_object (task); + + if (!(mapped_file = mapped_file_new_finish (result, &error))) + g_task_return_error (task, g_steal_pointer (&error)); + else + _sysprof_document_new_async (mapped_file, + set_progress, + g_object_ref (self), + g_object_unref, + g_task_get_cancellable (task), + sysprof_document_loader_load_document_cb, + g_object_ref (task)); +} + +/** + * sysprof_document_loader_load_async: + * @self: a #SysprofDocumentLoader + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: a callback to execute upon completion + * @user_data: closure data for @callback + * + * Asynchronously loads the document. + * + * @callback should call sysprof_document_loader_load_finish(). + */ +void +sysprof_document_loader_load_async (SysprofDocumentLoader *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(SysprofSymbolizer) symbolizer = NULL; + g_autoptr(GMappedFile) mapped_file = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GTask) task = NULL; + + g_return_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_return_if_fail (self->filename != NULL || self->fd != -1); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_task_data (task, g_object_ref (self->symbolizer), g_object_unref); + g_task_set_source_tag (task, sysprof_document_loader_load_async); + + set_progress (0., _("Loading document"), self); + + if (self->fd != -1) + mapped_file_new_from_fd_async (self->fd, + cancellable, + sysprof_document_loader_load_mapped_file_cb, + g_steal_pointer (&task)); + else + mapped_file_new_async (self->filename, + cancellable, + sysprof_document_loader_load_mapped_file_cb, + g_steal_pointer (&task)); +} + +/** + * sysprof_document_loader_load_finish: + * @self: a #SysprofDocumentLoader + * @result: a #GAsyncResult + * @error: a location for a #GError, or %NULL + * + * Completes a request to load a document asynchronously. + * + * Returns: (transfer full): a #SysprofDocumentLoader or %NULL + * and @error is set. + */ +SysprofDocument * +sysprof_document_loader_load_finish (SysprofDocumentLoader *self, + GAsyncResult *result, + GError **error) +{ + SysprofDocument *ret; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + + set_progress (1., NULL, self); + + ret = g_task_propagate_pointer (G_TASK (result), error); + + g_return_val_if_fail (!ret || SYSPROF_IS_DOCUMENT (ret), NULL); + + return ret; +} + +typedef struct SysprofDocumentLoaderSync +{ + GMainContext *main_context; + SysprofDocument *document; + GError *error; +} SysprofDocumentLoaderSync; + +static void +sysprof_document_loader_load_sync_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocumentLoader *loader = (SysprofDocumentLoader *)object; + SysprofDocumentLoaderSync *state = user_data; + + g_assert (SYSPROF_IS_DOCUMENT_LOADER (loader)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (state != NULL); + g_assert (state->main_context != NULL); + + state->document = sysprof_document_loader_load_finish (loader, result, &state->error); + + g_main_context_wakeup (state->main_context); +} + +/** + * sysprof_document_loader_load: + * @self: a #SysprofDocumentLoader + * @cancellable: (nullable): a #GCancellable or %NULL + * @error: a location for a #GError, or %NULL + * + * Synchronously loads the document. + * + * This function requires a #GMainContext to be set for the current + * thread and uses the asynchronously loader API underneath. + * + * Returns: (transfer full): a #SysprofDocument if successful; otherwise + * %NULL and @error is set. + */ +SysprofDocument * +sysprof_document_loader_load (SysprofDocumentLoader *self, + GCancellable *cancellable, + GError **error) +{ + SysprofDocumentLoaderSync state; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOADER (self), NULL); + g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); + + state.main_context = g_main_context_ref_thread_default (); + state.document = NULL; + state.error = NULL; + + sysprof_document_loader_load_async (self, + cancellable, + sysprof_document_loader_load_sync_cb, + &state); + + while (state.document == NULL && state.error == NULL) + g_main_context_iteration (state.main_context, TRUE); + + g_main_context_unref (state.main_context); + + if (state.error != NULL) + g_propagate_error (error, state.error); + + return state.document; +} diff --git a/src/libsysprof/sysprof-document-loader.h b/src/libsysprof/sysprof-document-loader.h new file mode 100644 index 00000000..289dae30 --- /dev/null +++ b/src/libsysprof/sysprof-document-loader.h @@ -0,0 +1,65 @@ +/* sysprof-document-loader.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-document.h" +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_LOADER (sysprof_document_loader_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofDocumentLoader, sysprof_document_loader, SYSPROF, DOCUMENT_LOADER, GObject) + +SYSPROF_AVAILABLE_IN_ALL +SysprofDocumentLoader *sysprof_document_loader_new (const char *filename); +SYSPROF_AVAILABLE_IN_ALL +SysprofDocumentLoader *sysprof_document_loader_new_for_fd (int fd, + GError **error); +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_document_loader_get_symbolizer (SysprofDocumentLoader *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_loader_set_symbolizer (SysprofDocumentLoader *self, + SysprofSymbolizer *symbolizer); +SYSPROF_AVAILABLE_IN_ALL +double sysprof_document_loader_get_fraction (SysprofDocumentLoader *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_loader_get_message (SysprofDocumentLoader *self); +SYSPROF_AVAILABLE_IN_ALL +SysprofDocument *sysprof_document_loader_load (SysprofDocumentLoader *self, + GCancellable *cancellable, + GError **error); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_loader_load_async (SysprofDocumentLoader *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +SysprofDocument *sysprof_document_loader_load_finish (SysprofDocumentLoader *self, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-log.c b/src/libsysprof/sysprof-document-log.c new file mode 100644 index 00000000..4a7dbc5e --- /dev/null +++ b/src/libsysprof/sysprof-document-log.c @@ -0,0 +1,144 @@ +/* sysprof-document-log.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-log.h" + +struct _SysprofDocumentLog +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentLogClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_DOMAIN, + PROP_MESSAGE, + PROP_SEVERITY, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentLog, sysprof_document_log, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_log_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentLog *self = SYSPROF_DOCUMENT_LOG (object); + + switch (prop_id) + { + case PROP_SEVERITY: + g_value_set_uint (value, sysprof_document_log_get_severity (self)); + break; + + case PROP_MESSAGE: + g_value_set_string (value, sysprof_document_log_get_message (self)); + break; + + case PROP_DOMAIN: + g_value_set_string (value, sysprof_document_log_get_domain (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_log_class_init (SysprofDocumentLogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofDocumentFrameClass *document_frame_class = SYSPROF_DOCUMENT_FRAME_CLASS (klass); + + object_class->get_property = sysprof_document_log_get_property; + + document_frame_class->type_name = N_("Log"); + + properties [PROP_SEVERITY] = + g_param_spec_uint ("severity", NULL, NULL, + 0, G_MAXUINT16, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DOMAIN] = + g_param_spec_string ("domain", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MESSAGE] = + g_param_spec_string ("message", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_log_init (SysprofDocumentLog *self) +{ +} + +GLogLevelFlags +sysprof_document_log_get_severity (SysprofDocumentLog *self) +{ + const SysprofCaptureLog *log; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOG (self), 0); + + log = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureLog); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (self, log->severity); +} + +const char * +sysprof_document_log_get_message (SysprofDocumentLog *self) +{ + const SysprofCaptureLog *log; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOG (self), 0); + + log = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureLog); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, log->message); +} + +const char * +sysprof_document_log_get_domain (SysprofDocumentLog *self) +{ + const SysprofCaptureLog *log; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_LOG (self), 0); + + log = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureLog); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, log->domain); +} diff --git a/src/libsysprof/sysprof-document-log.h b/src/libsysprof/sysprof-document-log.h new file mode 100644 index 00000000..191c9d91 --- /dev/null +++ b/src/libsysprof/sysprof-document-log.h @@ -0,0 +1,46 @@ +/* sysprof-document-log.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_LOG (sysprof_document_log_get_type()) +#define SYSPROF_IS_DOCUMENT_LOG(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_LOG) +#define SYSPROF_DOCUMENT_LOG(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_LOG, SysprofDocumentLog) +#define SYSPROF_DOCUMENT_LOG_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_LOG, SysprofDocumentLogClass) + +typedef struct _SysprofDocumentLog SysprofDocumentLog; +typedef struct _SysprofDocumentLogClass SysprofDocumentLogClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_log_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_log_get_message (SysprofDocumentLog *self); +SYSPROF_AVAILABLE_IN_ALL +GLogLevelFlags sysprof_document_log_get_severity (SysprofDocumentLog *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_log_get_domain (SysprofDocumentLog *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentLog, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-mark.c b/src/libsysprof/sysprof-document-mark.c new file mode 100644 index 00000000..66698fe6 --- /dev/null +++ b/src/libsysprof/sysprof-document-mark.c @@ -0,0 +1,204 @@ +/* sysprof-document-mark.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-mark.h" + +struct _SysprofDocumentMark +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentMarkClass +{ + SysprofDocumentFrameClass parent_class; +}; + + +enum { + PROP_0, + PROP_DURATION, + PROP_END_TIME, + PROP_GROUP, + PROP_MESSAGE, + PROP_NAME, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentMark, sysprof_document_mark, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static char * +sysprof_document_mark_dup_tooltip (SysprofDocumentFrame *frame) +{ + SysprofDocumentMark *self = (SysprofDocumentMark *)frame; + g_autofree char *time_string = NULL; + + g_assert (SYSPROF_IS_DOCUMENT_MARK (self)); + + time_string = sysprof_document_frame_dup_time_string (SYSPROF_DOCUMENT_FRAME (self)); + + return g_strdup_printf ("%s: %s: %s: %s", + time_string, + sysprof_document_mark_get_group (self), + sysprof_document_mark_get_name (self), + sysprof_document_mark_get_message (self)); +} + +static void +sysprof_document_mark_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentMark *self = SYSPROF_DOCUMENT_MARK (object); + + switch (prop_id) + { + case PROP_DURATION: + g_value_set_int64 (value, sysprof_document_mark_get_duration (self)); + break; + + case PROP_END_TIME: + g_value_set_int64 (value, sysprof_document_mark_get_end_time (self)); + break; + + case PROP_NAME: + g_value_set_string (value, sysprof_document_mark_get_name (self)); + break; + + case PROP_GROUP: + g_value_set_string (value, sysprof_document_mark_get_group (self)); + break; + + case PROP_MESSAGE: + g_value_set_string (value, sysprof_document_mark_get_message (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_mark_class_init (SysprofDocumentMarkClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofDocumentFrameClass *document_frame_class = SYSPROF_DOCUMENT_FRAME_CLASS (klass); + + object_class->get_property = sysprof_document_mark_get_property; + + document_frame_class->type_name = N_("Mark"); + document_frame_class->dup_tooltip = sysprof_document_mark_dup_tooltip; + + properties [PROP_DURATION] = + g_param_spec_int64 ("duration", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_END_TIME] = + g_param_spec_int64 ("end-time", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_GROUP] = + g_param_spec_string ("group", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_NAME] = + g_param_spec_string ("name", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MESSAGE] = + g_param_spec_string ("message", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_mark_init (SysprofDocumentMark *self) +{ +} + +gint64 +sysprof_document_mark_get_duration (SysprofDocumentMark *self) +{ + const SysprofCaptureMark *mark; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MARK (self), 0); + + mark = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMark); + + return SYSPROF_DOCUMENT_FRAME_INT64 (self, mark->duration); +} + +const char * +sysprof_document_mark_get_group (SysprofDocumentMark *self) +{ + const SysprofCaptureMark *mark; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MARK (self), 0); + + mark = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMark); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, mark->group); +} + +const char * +sysprof_document_mark_get_name (SysprofDocumentMark *self) +{ + const SysprofCaptureMark *mark; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MARK (self), 0); + + mark = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMark); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, mark->name); +} + +const char * +sysprof_document_mark_get_message (SysprofDocumentMark *self) +{ + const SysprofCaptureMark *mark; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MARK (self), 0); + + mark = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMark); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, mark->message); +} + +gint64 +sysprof_document_mark_get_end_time (SysprofDocumentMark *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MARK (self), 0); + + return sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (self)) + + sysprof_document_mark_get_duration (self); +} diff --git a/src/libsysprof/sysprof-document-mark.h b/src/libsysprof/sysprof-document-mark.h new file mode 100644 index 00000000..3be3c28b --- /dev/null +++ b/src/libsysprof/sysprof-document-mark.h @@ -0,0 +1,51 @@ +/* sysprof-document-mark.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_MARK (sysprof_document_mark_get_type()) +#define SYSPROF_IS_DOCUMENT_MARK(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_MARK) +#define SYSPROF_DOCUMENT_MARK(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_MARK, SysprofDocumentMark) +#define SYSPROF_DOCUMENT_MARK_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_MARK, SysprofDocumentMarkClass) + +typedef struct _SysprofDocumentMark SysprofDocumentMark; +typedef struct _SysprofDocumentMarkClass SysprofDocumentMarkClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_mark_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_mark_get_duration (SysprofDocumentMark *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_mark_get_group (SysprofDocumentMark *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_mark_get_name (SysprofDocumentMark *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_mark_get_message (SysprofDocumentMark *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_mark_get_end_time (SysprofDocumentMark *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentMark, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-metadata.c b/src/libsysprof/sysprof-document-metadata.c new file mode 100644 index 00000000..f2e5905f --- /dev/null +++ b/src/libsysprof/sysprof-document-metadata.c @@ -0,0 +1,117 @@ +/* sysprof-document-metadata.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-metadata.h" + +struct _SysprofDocumentMetadata +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentMetadataClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_ID, + PROP_VALUE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentMetadata, sysprof_document_metadata, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_metadata_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentMetadata *self = SYSPROF_DOCUMENT_METADATA (object); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, sysprof_document_metadata_get_id (self)); + break; + + case PROP_VALUE: + g_value_set_string (value, sysprof_document_metadata_get_value (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_metadata_class_init (SysprofDocumentMetadataClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_metadata_get_property; + + properties [PROP_ID] = + g_param_spec_string ("id", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_VALUE] = + g_param_spec_string ("value", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_metadata_init (SysprofDocumentMetadata *self) +{ +} + +const char * +sysprof_document_metadata_get_id (SysprofDocumentMetadata *self) +{ + const SysprofCaptureMetadata *meta; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_METADATA (self), 0); + + meta = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMetadata); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, meta->id); +} + +const char * +sysprof_document_metadata_get_value (SysprofDocumentMetadata *self) +{ + const SysprofCaptureMetadata *meta; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_METADATA (self), 0); + + meta = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMetadata); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, meta->metadata); +} diff --git a/src/libsysprof/sysprof-document-metadata.h b/src/libsysprof/sysprof-document-metadata.h new file mode 100644 index 00000000..de5672a7 --- /dev/null +++ b/src/libsysprof/sysprof-document-metadata.h @@ -0,0 +1,45 @@ +/* sysprof-document-metadata.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_METADATA (sysprof_document_metadata_get_type()) +#define SYSPROF_IS_DOCUMENT_METADATA(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_METADATA) +#define SYSPROF_DOCUMENT_METADATA(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_METADATA, SysprofDocumentMetadata) +#define SYSPROF_DOCUMENT_METADATA_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_METADATA, SysprofDocumentMetadataClass) + +typedef struct _SysprofDocumentMetadata SysprofDocumentMetadata; +typedef struct _SysprofDocumentMetadataClass SysprofDocumentMetadataClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_metadata_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_metadata_get_id (SysprofDocumentMetadata *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_metadata_get_value (SysprofDocumentMetadata *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentMetadata, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-mmap.c b/src/libsysprof/sysprof-document-mmap.c new file mode 100644 index 00000000..50bc5351 --- /dev/null +++ b/src/libsysprof/sysprof-document-mmap.c @@ -0,0 +1,223 @@ +/* + * sysprof-document-mmap.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-mmap.h" + +struct _SysprofDocumentMmap +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentMmapClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_BUILD_ID, + PROP_END_ADDRESS, + PROP_FILE, + PROP_FILE_INODE, + PROP_FILE_OFFSET, + PROP_START_ADDRESS, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentMmap, sysprof_document_mmap, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_mmap_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentMmap *self = SYSPROF_DOCUMENT_MMAP (object); + + switch (prop_id) + { + case PROP_START_ADDRESS: + g_value_set_uint64 (value, sysprof_document_mmap_get_start_address (self)); + break; + + case PROP_END_ADDRESS: + g_value_set_uint64 (value, sysprof_document_mmap_get_end_address (self)); + break; + + case PROP_FILE: + g_value_set_string (value, sysprof_document_mmap_get_file (self)); + break; + + case PROP_FILE_OFFSET: + g_value_set_uint64 (value, sysprof_document_mmap_get_file_offset (self)); + break; + + case PROP_FILE_INODE: + g_value_set_uint64 (value, sysprof_document_mmap_get_file_inode (self)); + break; + + case PROP_BUILD_ID: + g_value_set_string (value, sysprof_document_mmap_get_build_id (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_mmap_class_init (SysprofDocumentMmapClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_mmap_get_property; + + properties [PROP_START_ADDRESS] = + g_param_spec_uint64 ("start-address", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_END_ADDRESS] = + g_param_spec_uint64 ("end-address", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILE] = + g_param_spec_string ("file", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILE_INODE] = + g_param_spec_uint64 ("file-inode", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILE_OFFSET] = + g_param_spec_uint64 ("file-offset", NULL, NULL, + 0, G_MAXUINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BUILD_ID] = + g_param_spec_string ("build-id", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_mmap_init (SysprofDocumentMmap *self) +{ +} + +guint64 +sysprof_document_mmap_get_start_address (SysprofDocumentMmap *self) +{ + const SysprofCaptureMap *mmap; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MMAP (self), 0); + + mmap = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMap); + + return SYSPROF_DOCUMENT_FRAME_UINT64 (self, mmap->start); +} + +guint64 +sysprof_document_mmap_get_end_address (SysprofDocumentMmap *self) +{ + const SysprofCaptureMap *mmap; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MMAP (self), 0); + + mmap = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMap); + + return SYSPROF_DOCUMENT_FRAME_UINT64 (self, mmap->end); +} + +guint64 +sysprof_document_mmap_get_file_inode (SysprofDocumentMmap *self) +{ + const SysprofCaptureMap *mmap; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MMAP (self), 0); + + mmap = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMap); + + return SYSPROF_DOCUMENT_FRAME_UINT64 (self, mmap->inode); +} + +guint64 +sysprof_document_mmap_get_file_offset (SysprofDocumentMmap *self) +{ + const SysprofCaptureMap *mmap; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MMAP (self), 0); + + mmap = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMap); + + return SYSPROF_DOCUMENT_FRAME_UINT64 (self, mmap->offset); +} + +const char * +sysprof_document_mmap_get_file (SysprofDocumentMmap *self) +{ + const SysprofCaptureMap *mmap; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MMAP (self), NULL); + + mmap = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureMap); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, mmap->filename); +} + +const char * +sysprof_document_mmap_get_build_id (SysprofDocumentMmap *self) +{ + const char *file; + const char *build_id; + gsize len; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_MMAP (self), NULL); + + if (!(file = sysprof_document_mmap_get_file (self))) + return NULL; + + /* The build-id may be tacked on after the filename if after the + * Nil byte we get '@'. SYSPROF_DOCUMENT_FRAME_CSTRING() will check + * for bounds so we can feed it a position we don't know is part + * of our frame or not. We expect "FILE\0@BUILD_ID_IN_HEX\0". + */ + + len = strlen (file); + + if (!(build_id = SYSPROF_DOCUMENT_FRAME_CSTRING (self, &file[len+1]))) + return NULL; + + if (build_id[0] != '@') + return NULL; + + return &build_id[1]; +} diff --git a/src/libsysprof/sysprof-document-mmap.h b/src/libsysprof/sysprof-document-mmap.h new file mode 100644 index 00000000..65451730 --- /dev/null +++ b/src/libsysprof/sysprof-document-mmap.h @@ -0,0 +1,53 @@ +/* + * sysprof-document-mmap.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_MMAP (sysprof_document_mmap_get_type()) +#define SYSPROF_IS_DOCUMENT_MMAP(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_MMAP) +#define SYSPROF_DOCUMENT_MMAP(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_MMAP, SysprofDocumentMmap) +#define SYSPROF_DOCUMENT_MMAP_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_MMAP, SysprofDocumentMmapClass) + +typedef struct _SysprofDocumentMmap SysprofDocumentMmap; +typedef struct _SysprofDocumentMmapClass SysprofDocumentMmapClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_mmap_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +guint64 sysprof_document_mmap_get_start_address (SysprofDocumentMmap *self); +SYSPROF_AVAILABLE_IN_ALL +guint64 sysprof_document_mmap_get_end_address (SysprofDocumentMmap *self); +SYSPROF_AVAILABLE_IN_ALL +guint64 sysprof_document_mmap_get_file_inode (SysprofDocumentMmap *self); +SYSPROF_AVAILABLE_IN_ALL +guint64 sysprof_document_mmap_get_file_offset (SysprofDocumentMmap *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_mmap_get_file (SysprofDocumentMmap *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_mmap_get_build_id (SysprofDocumentMmap *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentMmap, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-overlay.c b/src/libsysprof/sysprof-document-overlay.c new file mode 100644 index 00000000..ac42ffa0 --- /dev/null +++ b/src/libsysprof/sysprof-document-overlay.c @@ -0,0 +1,142 @@ +/* sysprof-document-overlay.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-overlay.h" + +struct _SysprofDocumentOverlay +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentOverlayClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_DESTINATION, + PROP_LAYER, + PROP_SOURCE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentOverlay, sysprof_document_overlay, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_overlay_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentOverlay *self = SYSPROF_DOCUMENT_OVERLAY (object); + + switch (prop_id) + { + case PROP_DESTINATION: + g_value_set_string (value, sysprof_document_overlay_get_destination (self)); + break; + + case PROP_LAYER: + g_value_set_uint (value, sysprof_document_overlay_get_layer (self)); + break; + + case PROP_SOURCE: + g_value_set_string (value, sysprof_document_overlay_get_source (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_overlay_class_init (SysprofDocumentOverlayClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = sysprof_document_overlay_get_property; + + properties [PROP_LAYER] = + g_param_spec_uint ("layer", NULL, NULL, + 0, G_MAXUINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DESTINATION] = + g_param_spec_string ("destination", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SOURCE] = + g_param_spec_string ("source", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_overlay_init (SysprofDocumentOverlay *self) +{ +} + +guint +sysprof_document_overlay_get_layer (SysprofDocumentOverlay *self) +{ + const SysprofCaptureOverlay *overlay; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_OVERLAY (self), 0); + + overlay = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureOverlay); + + return overlay->layer; +} + +const char * +sysprof_document_overlay_get_source (SysprofDocumentOverlay *self) +{ + const SysprofCaptureOverlay *overlay; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_OVERLAY (self), 0); + + overlay = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureOverlay); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, overlay->data); +} + +const char * +sysprof_document_overlay_get_destination (SysprofDocumentOverlay *self) +{ + const SysprofCaptureOverlay *overlay; + guint16 offset; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_OVERLAY (self), 0); + + overlay = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureOverlay); + + offset = SYSPROF_DOCUMENT_FRAME_UINT16 (self, overlay->src_len); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, &overlay->data[offset+1]); +} diff --git a/src/libsysprof/sysprof-document-overlay.h b/src/libsysprof/sysprof-document-overlay.h new file mode 100644 index 00000000..86705507 --- /dev/null +++ b/src/libsysprof/sysprof-document-overlay.h @@ -0,0 +1,47 @@ +/* sysprof-document-overlay.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_OVERLAY (sysprof_document_overlay_get_type()) +#define SYSPROF_IS_DOCUMENT_OVERLAY(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_OVERLAY) +#define SYSPROF_DOCUMENT_OVERLAY(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_OVERLAY, SysprofDocumentOverlay) +#define SYSPROF_DOCUMENT_OVERLAY_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_OVERLAY, SysprofDocumentOverlayClass) + +typedef struct _SysprofDocumentOverlay SysprofDocumentOverlay; +typedef struct _SysprofDocumentOverlayClass SysprofDocumentOverlayClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_overlay_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_overlay_get_layer (SysprofDocumentOverlay *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_overlay_get_source (SysprofDocumentOverlay *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_overlay_get_destination (SysprofDocumentOverlay *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentOverlay, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-private.h b/src/libsysprof/sysprof-document-private.h new file mode 100644 index 00000000..22578c51 --- /dev/null +++ b/src/libsysprof/sysprof-document-private.h @@ -0,0 +1,78 @@ +/* sysprof-document-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document.h" +#include "sysprof-symbolizer.h" +#include "sysprof-symbol.h" + +G_BEGIN_DECLS + +typedef struct _SysprofDocumentTimedValue +{ + gint64 time; + union { + gint64 v_int64; + double v_double; + guint8 v_raw[8]; + }; +} SysprofDocumentTimedValue; + +typedef void (*ProgressFunc) (double fraction, + const char *message, + gpointer user_data); + +void _sysprof_document_new_async (GMappedFile *mapped_file, + ProgressFunc progress, + gpointer progress_data, + GDestroyNotify progress_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SysprofDocument *_sysprof_document_new_finish (GAsyncResult *result, + GError **error); +void _sysprof_document_set_title (SysprofDocument *self, + const char *title); +void _sysprof_document_symbolize_async (SysprofDocument *self, + SysprofSymbolizer *symbolizer, + ProgressFunc progress_func, + gpointer progress_data, + GDestroyNotify progress_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean _sysprof_document_symbolize_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error); +gboolean _sysprof_document_is_native (SysprofDocument *self); +GRefString *_sysprof_document_ref_string (SysprofDocument *self, + const char *name); +EggBitset *_sysprof_document_traceables (SysprofDocument *self); +SysprofSymbol *_sysprof_document_process_symbol (SysprofDocument *self, + int pid); +SysprofSymbol *_sysprof_document_thread_symbol (SysprofDocument *self, + int pid, + int tid); +SysprofSymbol *_sysprof_document_kernel_symbol (SysprofDocument *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-process-private.h b/src/libsysprof/sysprof-document-process-private.h new file mode 100644 index 00000000..e345e15a --- /dev/null +++ b/src/libsysprof/sysprof-document-process-private.h @@ -0,0 +1,32 @@ +/* sysprof-document-process-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-process.h" +#include "sysprof-process-info-private.h" + +G_BEGIN_DECLS + +SysprofProcessInfo *_sysprof_document_process_get_info (SysprofDocumentProcess *self); +void _sysprof_document_process_set_info (SysprofDocumentProcess *self, + SysprofProcessInfo *process_info); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-process.c b/src/libsysprof/sysprof-document-process.c new file mode 100644 index 00000000..ca348983 --- /dev/null +++ b/src/libsysprof/sysprof-document-process.c @@ -0,0 +1,323 @@ +/* sysprof-document-process.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-frame-private.h" +#include "sysprof-document-process-private.h" +#include "sysprof-mount.h" +#include "sysprof-thread-info.h" + +struct _SysprofDocumentProcess +{ + SysprofDocumentFrame parent_instance; + SysprofProcessInfo *process_info; +}; + +struct _SysprofDocumentProcessClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_COMMAND_LINE, + PROP_DURATION, + PROP_MEMORY_MAPS, + PROP_MOUNTS, + PROP_EXIT_TIME, + PROP_THREADS, + PROP_TITLE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDocumentProcess, sysprof_document_process, SYSPROF_TYPE_DOCUMENT_FRAME) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_process_finalize (GObject *object) +{ + SysprofDocumentProcess *self = (SysprofDocumentProcess *)object; + + g_clear_pointer (&self->process_info, sysprof_process_info_unref); + + G_OBJECT_CLASS (sysprof_document_process_parent_class)->finalize (object); +} + +static void +sysprof_document_process_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentProcess *self = SYSPROF_DOCUMENT_PROCESS (object); + + switch (prop_id) + { + case PROP_COMMAND_LINE: + g_value_set_string (value, sysprof_document_process_get_command_line (self)); + break; + + case PROP_DURATION: + g_value_set_int64 (value, sysprof_document_process_get_duration (self)); + break; + + case PROP_EXIT_TIME: + g_value_set_int64 (value, sysprof_document_process_get_exit_time (self)); + break; + + case PROP_MEMORY_MAPS: + g_value_take_object (value, sysprof_document_process_list_memory_maps (self)); + break; + + case PROP_MOUNTS: + g_value_take_object (value, sysprof_document_process_list_mounts (self)); + break; + + case PROP_THREADS: + g_value_take_object (value, sysprof_document_process_list_threads (self)); + break; + + case PROP_TITLE: + g_value_take_string (value, sysprof_document_process_dup_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_process_class_init (SysprofDocumentProcessClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_process_finalize; + object_class->get_property = sysprof_document_process_get_property; + + properties [PROP_COMMAND_LINE] = + g_param_spec_string ("command-line", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DURATION] = + g_param_spec_int64 ("duration", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_EXIT_TIME] = + g_param_spec_int64 ("exit-time", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MEMORY_MAPS] = + g_param_spec_object ("memory-maps", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MOUNTS] = + g_param_spec_object ("mounts", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_THREADS] = + g_param_spec_object ("threads", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_process_init (SysprofDocumentProcess *self) +{ +} + +const char * +sysprof_document_process_get_command_line (SysprofDocumentProcess *self) +{ + const SysprofCaptureProcess *proc; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), 0); + + proc = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureProcess); + + return SYSPROF_DOCUMENT_FRAME_CSTRING (self, proc->cmdline); +} + +/** + * sysprof_document_process_list_memory_maps: + * @self: a #SysprofDocumentProcess + * + * Lists the #SysprofDocumentMmap that are associated with the process. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentMmap + */ +GListModel * +sysprof_document_process_list_memory_maps (SysprofDocumentProcess *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), NULL); + + if (self->process_info == NULL) + return G_LIST_MODEL (g_list_store_new (SYSPROF_TYPE_DOCUMENT_MMAP)); + + return g_object_ref (G_LIST_MODEL (self->process_info->address_layout)); +} + +/** + * sysprof_document_process_list_mounts: + * @self: a #SysprofDocumentProcess + * + * Lists the #SysprofMount that are associated with the process. + * + * Returns: (transfer full): a #GListModel of #SysprofMount + */ +GListModel * +sysprof_document_process_list_mounts (SysprofDocumentProcess *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), NULL); + + if (self->process_info == NULL) + return G_LIST_MODEL (g_list_store_new (SYSPROF_TYPE_MOUNT)); + + return g_object_ref (G_LIST_MODEL (self->process_info->mount_namespace)); +} + +SysprofProcessInfo * +_sysprof_document_process_get_info (SysprofDocumentProcess *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), NULL); + + return self->process_info; +} + +void +_sysprof_document_process_set_info (SysprofDocumentProcess *self, + SysprofProcessInfo *process_info) +{ + g_return_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self)); + g_return_if_fail (process_info != NULL); + g_return_if_fail (self->process_info == NULL); + + self->process_info = sysprof_process_info_ref (process_info); +} + +gint64 +sysprof_document_process_get_exit_time (SysprofDocumentProcess *self) +{ + gint64 exit_time = 0; + gint64 t; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), 0); + + if (self->process_info != NULL) + exit_time = self->process_info->exit_time; + + t = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (self)); + + return MAX (t, exit_time); +} + +gint64 +sysprof_document_process_get_duration (SysprofDocumentProcess *self) +{ + gint64 t; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), 0); + + t = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (self)); + + return sysprof_document_process_get_exit_time (self) - t; +} + +/** + * sysprof_document_process_dup_title: + * @self: a #SysprofDocumentProcess + * + * Gets a suitable title for the process. + * + * Returns: (transfer full): a string containing a process title + */ +char * +sysprof_document_process_dup_title (SysprofDocumentProcess *self) +{ + const char *command_line; + int pid; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), NULL); + + pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (self)); + + if ((command_line = sysprof_document_process_get_command_line (self))) + return g_strdup_printf (_("%s [Process %d]"), command_line, pid); + + return g_strdup_printf (_("Process %d"), pid); +} + +/** + * sysprof_document_process_list_threads: + * @self: a #SysprofDocumentProcess + * + * Gets the list of threads for the process. + * + * Returns: (transfer full): a #GListModel of #SysprofThreadInfo. + */ +GListModel * +sysprof_document_process_list_threads (SysprofDocumentProcess *self) +{ + GListStore *store; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_PROCESS (self), NULL); + + store = g_list_store_new (SYSPROF_TYPE_THREAD_INFO); + + if (self->process_info != NULL) + { + g_autoptr(GPtrArray) threads = g_ptr_array_new_with_free_func (g_object_unref); + EggBitsetIter iter; + guint i; + + if (egg_bitset_iter_init_first (&iter, self->process_info->thread_ids, &i)) + { + do + { + g_ptr_array_add (threads, + g_object_new (SYSPROF_TYPE_THREAD_INFO, + "process", self, + "thread-id", i, + NULL)); + } + while (egg_bitset_iter_next (&iter, &i)); + } + + if (threads->len > 0) + g_list_store_splice (store, 0, 0, threads->pdata, threads->len); + } + + return G_LIST_MODEL (store); +} diff --git a/src/libsysprof/sysprof-document-process.h b/src/libsysprof/sysprof-document-process.h new file mode 100644 index 00000000..73f98d46 --- /dev/null +++ b/src/libsysprof/sysprof-document-process.h @@ -0,0 +1,57 @@ +/* sysprof-document-process.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_PROCESS (sysprof_document_process_get_type()) +#define SYSPROF_IS_DOCUMENT_PROCESS(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_PROCESS) +#define SYSPROF_DOCUMENT_PROCESS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_PROCESS, SysprofDocumentProcess) +#define SYSPROF_DOCUMENT_PROCESS_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_PROCESS, SysprofDocumentProcessClass) + +typedef struct _SysprofDocumentProcess SysprofDocumentProcess; +typedef struct _SysprofDocumentProcessClass SysprofDocumentProcessClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_process_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_document_process_get_command_line (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_process_get_exit_time (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_document_process_get_duration (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_document_process_dup_title (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_process_list_memory_maps (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_process_list_mounts (SysprofDocumentProcess *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_process_list_threads (SysprofDocumentProcess *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentProcess, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-document-sample.c b/src/libsysprof/sysprof-document-sample.c new file mode 100644 index 00000000..3eb612a6 --- /dev/null +++ b/src/libsysprof/sysprof-document-sample.c @@ -0,0 +1,170 @@ +/* sysprof-document-sample.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-frame-private.h" + +#include "sysprof-document-sample.h" +#include "sysprof-document-traceable.h" + +struct _SysprofDocumentSample +{ + SysprofDocumentFrame parent_instance; +}; + +struct _SysprofDocumentSampleClass +{ + SysprofDocumentFrameClass parent_class; +}; + +enum { + PROP_0, + PROP_STACK_DEPTH, + PROP_THREAD_ID, + N_PROPS +}; + +static guint +sysprof_document_sample_get_stack_depth (SysprofDocumentTraceable *traceable) +{ + const SysprofCaptureSample *sample = SYSPROF_DOCUMENT_FRAME_GET (traceable, SysprofCaptureSample); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (traceable, sample->n_addrs); +} + +static guint64 +sysprof_document_sample_get_stack_address (SysprofDocumentTraceable *traceable, + guint position) +{ + const SysprofCaptureSample *sample = SYSPROF_DOCUMENT_FRAME_GET (traceable, SysprofCaptureSample); + + return SYSPROF_DOCUMENT_FRAME_UINT16 (traceable, sample->addrs[position]); +} + +static guint +sysprof_document_sample_get_stack_addresses (SysprofDocumentTraceable *traceable, + guint64 *addresses, + guint n_addresses) +{ + const SysprofCaptureSample *sample = SYSPROF_DOCUMENT_FRAME_GET (traceable, SysprofCaptureSample); + guint depth = MIN (n_addresses, SYSPROF_DOCUMENT_FRAME_UINT16 (traceable, sample->n_addrs)); + + for (guint i = 0; i < depth; i++) + addresses[i] = SYSPROF_DOCUMENT_FRAME_UINT64 (traceable, sample->addrs[i]); + + return depth; +} + +static int +sysprof_document_sample_get_thread_id (SysprofDocumentTraceable *traceable) +{ + SysprofDocumentSample *self = (SysprofDocumentSample *)traceable; + const SysprofCaptureSample *sample; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_SAMPLE (self), -1); + + sample = SYSPROF_DOCUMENT_FRAME_GET (self, SysprofCaptureSample); + + return SYSPROF_DOCUMENT_FRAME_INT32 (self, sample->tid); +} + +static void +traceable_iface_init (SysprofDocumentTraceableInterface *iface) +{ + iface->get_stack_depth = sysprof_document_sample_get_stack_depth; + iface->get_stack_address = sysprof_document_sample_get_stack_address; + iface->get_stack_addresses = sysprof_document_sample_get_stack_addresses; + iface->get_thread_id = sysprof_document_sample_get_thread_id; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocumentSample, sysprof_document_sample, SYSPROF_TYPE_DOCUMENT_FRAME, + G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_DOCUMENT_TRACEABLE, traceable_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_document_sample_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocumentSample *self = SYSPROF_DOCUMENT_SAMPLE (object); + + switch (prop_id) + { + case PROP_STACK_DEPTH: + g_value_set_uint (value, sysprof_document_traceable_get_stack_depth (SYSPROF_DOCUMENT_TRACEABLE (self))); + break; + + case PROP_THREAD_ID: + g_value_set_int (value, sysprof_document_sample_get_thread_id (SYSPROF_DOCUMENT_TRACEABLE (self))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_sample_class_init (SysprofDocumentSampleClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofDocumentFrameClass *document_frame_class = SYSPROF_DOCUMENT_FRAME_CLASS (klass); + + object_class->get_property = sysprof_document_sample_get_property; + + document_frame_class->type_name = N_("Sample"); + + /** + * SysprofDocumentSample:thread-id: + * + * The thread-id where the sample occurred. + * + * On Linux, this is generally set to the value of gettid(). + * + * Since: 45 + */ + properties [PROP_THREAD_ID] = + g_param_spec_int ("thread-id", NULL, NULL, + -1, G_MAXINT32, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * SysprofDocumentSample:stack-depth: + * + * The depth of the stack trace. + * + * Since: 45 + */ + properties [PROP_STACK_DEPTH] = + g_param_spec_uint ("stack-depth", NULL, NULL, + 0, G_MAXUINT32, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_sample_init (SysprofDocumentSample *self) +{ +} diff --git a/src/libsysprof/sysprof-document-sample.h b/src/libsysprof/sysprof-document-sample.h new file mode 100644 index 00000000..b722540b --- /dev/null +++ b/src/libsysprof/sysprof-document-sample.h @@ -0,0 +1,42 @@ +/* sysprof-document-sample.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_SAMPLE (sysprof_document_sample_get_type()) +#define SYSPROF_IS_DOCUMENT_SAMPLE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_DOCUMENT_SAMPLE) +#define SYSPROF_DOCUMENT_SAMPLE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_DOCUMENT_SAMPLE, SysprofDocumentSample) +#define SYSPROF_DOCUMENT_SAMPLE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_DOCUMENT_SAMPLE, SysprofDocumentSampleClass) + +typedef struct _SysprofDocumentSample SysprofDocumentSample; +typedef struct _SysprofDocumentSampleClass SysprofDocumentSampleClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_document_sample_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_sample_get_tid (SysprofDocumentSample *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofDocumentSample, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-symbols-private.h b/src/libsysprof/sysprof-document-symbols-private.h new file mode 100644 index 00000000..9f0dcc65 --- /dev/null +++ b/src/libsysprof/sysprof-document-symbols-private.h @@ -0,0 +1,59 @@ +/* sysprof-document-symbols-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document.h" +#include "sysprof-process-info-private.h" +#include "sysprof-symbol-cache-private.h" +#include "sysprof-symbol.h" +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_SYMBOLS (sysprof_document_symbols_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofDocumentSymbols, sysprof_document_symbols, SYSPROF, DOCUMENT_SYMBOLS, GObject) + +struct _SysprofDocumentSymbols +{ + GObject parent_instance; + SysprofSymbol *context_switches[SYSPROF_ADDRESS_CONTEXT_GUEST_USER+1]; + SysprofSymbolCache *kernel_symbols; +}; + +void _sysprof_document_symbols_new (SysprofDocument *document, + SysprofStrings *strings, + SysprofSymbolizer *symbolizer, + GHashTable *pid_to_process_info, + ProgressFunc progress_func, + gpointer progress_data, + GDestroyNotify progress_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SysprofDocumentSymbols *_sysprof_document_symbols_new_finish (GAsyncResult *result, + GError **error); +SysprofSymbol *_sysprof_document_symbols_lookup (SysprofDocumentSymbols *symbols, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document-symbols.c b/src/libsysprof/sysprof-document-symbols.c new file mode 100644 index 00000000..2f8d1849 --- /dev/null +++ b/src/libsysprof/sysprof-document-symbols.c @@ -0,0 +1,307 @@ +/* sysprof-document-symbols.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-address-layout-private.h" +#include "sysprof-document-private.h" +#include "sysprof-document-symbols-private.h" +#include "sysprof-document-traceable.h" +#include "sysprof-mount-namespace-private.h" +#include "sysprof-no-symbolizer.h" +#include "sysprof-symbol-private.h" +#include "sysprof-symbolizer-private.h" + +G_DEFINE_FINAL_TYPE (SysprofDocumentSymbols, sysprof_document_symbols, G_TYPE_OBJECT) + +static void +sysprof_document_symbols_finalize (GObject *object) +{ + SysprofDocumentSymbols *self = (SysprofDocumentSymbols *)object; + + for (guint i = 0; i < G_N_ELEMENTS (self->context_switches); i++) + g_clear_object (&self->context_switches[i]); + + g_clear_object (&self->kernel_symbols); + + G_OBJECT_CLASS (sysprof_document_symbols_parent_class)->finalize (object); +} + +static void +sysprof_document_symbols_class_init (SysprofDocumentSymbolsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_symbols_finalize; +} + +static void +sysprof_document_symbols_init (SysprofDocumentSymbols *self) +{ + self->kernel_symbols = sysprof_symbol_cache_new (); +} + +typedef struct _Symbolize +{ + SysprofDocument *document; + SysprofSymbolizer *symbolizer; + SysprofDocumentSymbols *symbols; + SysprofStrings *strings; + GHashTable *pid_to_process_info; + ProgressFunc progress_func; + gpointer progress_data; + GDestroyNotify progress_data_destroy; +} Symbolize; + +static void +symbolize_free (Symbolize *state) +{ + if (state->progress_data_destroy) + state->progress_data_destroy (state->progress_data); + g_clear_object (&state->document); + g_clear_object (&state->symbolizer); + g_clear_object (&state->symbols); + g_clear_pointer (&state->strings, sysprof_strings_unref); + g_clear_pointer (&state->pid_to_process_info, g_hash_table_unref); + g_free (state); +} + +static void +add_traceable (SysprofDocumentSymbols *self, + SysprofStrings *strings, + SysprofProcessInfo *process_info, + SysprofDocumentTraceable *traceable, + SysprofSymbolizer *symbolizer) +{ + SysprofAddressContext last_context; + guint64 *addresses; + guint n_addresses; + + g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + g_assert (SYSPROF_IS_SYMBOLIZER (symbolizer)); + + n_addresses = sysprof_document_traceable_get_stack_depth (traceable); + addresses = g_alloca (sizeof (guint64) * n_addresses); + sysprof_document_traceable_get_stack_addresses (traceable, addresses, n_addresses); + + last_context = SYSPROF_ADDRESS_CONTEXT_NONE; + + for (guint i = 0; i < n_addresses; i++) + { + SysprofAddress address = addresses[i]; + SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; + + if (sysprof_address_is_context_switch (address, &context)) + { + last_context = context; + continue; + } + + if (last_context == SYSPROF_ADDRESS_CONTEXT_KERNEL) + { + g_autoptr(SysprofSymbol) symbol = NULL; + + if (sysprof_symbol_cache_lookup (self->kernel_symbols, address) != NULL) + continue; + + if ((symbol = _sysprof_symbolizer_symbolize (symbolizer, strings, process_info, last_context, address))) + sysprof_symbol_cache_take (self->kernel_symbols, g_steal_pointer (&symbol)); + } + else + { + g_autoptr(SysprofSymbol) symbol = NULL; + + if (process_info != NULL && + sysprof_symbol_cache_lookup (process_info->symbol_cache, address) != NULL) + continue; + + if ((symbol = _sysprof_symbolizer_symbolize (symbolizer, strings, process_info, last_context, address))) + sysprof_symbol_cache_take (process_info->symbol_cache, g_steal_pointer (&symbol)); + } + } +} + +static void +sysprof_document_symbols_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + static const struct { + const char *name; + guint value; + } context_switches[] = { + { "- - Hypervisor - -", SYSPROF_ADDRESS_CONTEXT_HYPERVISOR }, + { "- - Kernel - -", SYSPROF_ADDRESS_CONTEXT_KERNEL }, + { "- - User - -", SYSPROF_ADDRESS_CONTEXT_USER }, + { "- - Guest - -", SYSPROF_ADDRESS_CONTEXT_GUEST }, + { "- - Guest Kernel - -", SYSPROF_ADDRESS_CONTEXT_GUEST_KERNEL }, + { "- - Guest User - -", SYSPROF_ADDRESS_CONTEXT_GUEST_USER }, + }; + g_autoptr(GRefString) context_switch = g_ref_string_new_intern ("Context Switch"); + Symbolize *state = task_data; + EggBitsetIter iter; + EggBitset *bitset; + GListModel *model; + guint count = 0; + guint i; + + g_assert (source_object == NULL); + g_assert (G_IS_TASK (task)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + g_assert (state != NULL); + g_assert (SYSPROF_IS_DOCUMENT (state->document)); + g_assert (SYSPROF_IS_SYMBOLIZER (state->symbolizer)); + g_assert (SYSPROF_IS_DOCUMENT_SYMBOLS (state->symbols)); + + bitset = _sysprof_document_traceables (state->document); + model = G_LIST_MODEL (state->document); + + /* Create static symbols for context switch use */ + for (guint cs = 0; cs < G_N_ELEMENTS (context_switches); cs++) + { + g_autoptr(SysprofSymbol) symbol = _sysprof_symbol_new (g_ref_string_new_intern (context_switches[cs].name), + NULL, + g_ref_string_acquire (context_switch), + 0, 0, + SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH); + + /* TODO: It would be nice if we had enough insight from the capture header + * as to the host system, so we can show "vmlinuz" and "Linux" respectively + * for binary-path and binary-nick when the capture came from Linux. + */ + + state->symbols->context_switches[context_switches[cs].value] = g_steal_pointer (&symbol); + } + + /* Walk through the available traceables which need symbols extracted */ + if (!SYSPROF_IS_NO_SYMBOLIZER (state->symbolizer) && + egg_bitset_iter_init_first (&iter, bitset, &i)) + { + guint n_items = egg_bitset_get_size (bitset); + + do + { + g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (model, i); + int pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable)); + SysprofProcessInfo *process_info = g_hash_table_lookup (state->pid_to_process_info, GINT_TO_POINTER (pid)); + + add_traceable (state->symbols, + state->strings, + process_info, + traceable, + state->symbolizer); + + count++; + + if (state->progress_func != NULL && count % 100 == 0) + state->progress_func (count / (double)n_items, _("Symbolizing stack traces"), state->progress_data); + } + while (egg_bitset_iter_next (&iter, &i)); + } + + g_task_return_pointer (task, + g_object_ref (state->symbols), + g_object_unref); +} + +void +_sysprof_document_symbols_new (SysprofDocument *document, + SysprofStrings *strings, + SysprofSymbolizer *symbolizer, + GHashTable *pid_to_process_info, + ProgressFunc progress_func, + gpointer progress_data, + GDestroyNotify progress_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + Symbolize *state; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (document)); + g_return_if_fail (SYSPROF_IS_SYMBOLIZER (symbolizer)); + + state = g_new0 (Symbolize, 1); + state->document = g_object_ref (document); + state->symbolizer = g_object_ref (symbolizer); + state->symbols = g_object_new (SYSPROF_TYPE_DOCUMENT_SYMBOLS, NULL); + state->strings = sysprof_strings_ref (strings); + state->pid_to_process_info = g_hash_table_ref (pid_to_process_info); + state->progress_func = progress_func; + state->progress_data = progress_data; + state->progress_data_destroy = progress_data_destroy; + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, _sysprof_document_symbols_new); + g_task_set_task_data (task, state, (GDestroyNotify)symbolize_free); + g_task_run_in_thread (task, sysprof_document_symbols_worker); +} + +SysprofDocumentSymbols * +_sysprof_document_symbols_new_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_TASK (result), NULL); + g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == _sysprof_document_symbols_new, NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * _sysprof_document_symbols_lookup: + * @self: a #SysprofDocumentSymbols + * @process_info: (nullable): the process info if necessary + * @context: the #SysprofAddressContext for the address + * @address: a #SysprofAddress to lookup the symbol for + * + * Locates the symbol that is found at @address within @context of @pid. + * + * Returns: (transfer none) (nullable): a #SysprofSymbol or %NULL + */ +SysprofSymbol * +_sysprof_document_symbols_lookup (SysprofDocumentSymbols *self, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofAddressContext new_context; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_SYMBOLS (self), NULL); + g_return_val_if_fail (context <= SYSPROF_ADDRESS_CONTEXT_GUEST_USER, NULL); + + if (context == SYSPROF_ADDRESS_CONTEXT_NONE) + context = SYSPROF_ADDRESS_CONTEXT_USER; + + if (sysprof_address_is_context_switch (address, &new_context)) + return self->context_switches[context]; + + if (context == SYSPROF_ADDRESS_CONTEXT_KERNEL) + return sysprof_symbol_cache_lookup (self->kernel_symbols, address); + + if (process_info != NULL) + return sysprof_symbol_cache_lookup (process_info->symbol_cache, address); + + return NULL; +} diff --git a/src/libsysprof/sysprof-document-traceable.c b/src/libsysprof/sysprof-document-traceable.c new file mode 100644 index 00000000..d6fca4d7 --- /dev/null +++ b/src/libsysprof/sysprof-document-traceable.c @@ -0,0 +1,90 @@ +/* + * sysprof-document-traceable.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-document-traceable.h" + +G_DEFINE_INTERFACE (SysprofDocumentTraceable, sysprof_document_traceable, SYSPROF_TYPE_DOCUMENT_FRAME) + +static void +sysprof_document_traceable_default_init (SysprofDocumentTraceableInterface *iface) +{ + /** + * SysprofDocumentTraceable:stack-depth: + * + * The "stack-depth" property contains the number of addresses collected + * in the backtrace. + * + * You may use this value to retrieve the addresses from 0 to ("stack-depth"-1) + * by calling sysprof_document_traceable_get_stack_address(). + * + * Since: 45 + */ + g_object_interface_install_property (iface, + g_param_spec_uint ("stack-depth", NULL, NULL, + 0, G_MAXUINT16, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + /** + * SysprofDocumentTraceable:thread-id: + * + * The "thread-id" property contains the thread identifier for the traceable. + * + * Since: 45 + */ + g_object_interface_install_property (iface, + g_param_spec_int ("thread-id", NULL, NULL, + -1, G_MAXINT, -1, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); +} + +guint +sysprof_document_traceable_get_stack_depth (SysprofDocumentTraceable *self) +{ + return SYSPROF_DOCUMENT_TRACEABLE_GET_IFACE (self)->get_stack_depth (self); +} + +guint64 +sysprof_document_traceable_get_stack_address (SysprofDocumentTraceable *self, + guint position) +{ + return SYSPROF_DOCUMENT_TRACEABLE_GET_IFACE (self)->get_stack_address (self, position); +} + +int +sysprof_document_traceable_get_thread_id (SysprofDocumentTraceable *self) +{ + return SYSPROF_DOCUMENT_TRACEABLE_GET_IFACE (self)->get_thread_id (self); +} + +guint +sysprof_document_traceable_get_stack_addresses (SysprofDocumentTraceable *self, + guint64 *addresses, + guint n_addresses) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TRACEABLE (self), 0); + + if (addresses == NULL || n_addresses == 0) + return 0; + + return SYSPROF_DOCUMENT_TRACEABLE_GET_IFACE (self)->get_stack_addresses (self, addresses, n_addresses); +} diff --git a/src/libsysprof/sysprof-document-traceable.h b/src/libsysprof/sysprof-document-traceable.h new file mode 100644 index 00000000..4b55dec7 --- /dev/null +++ b/src/libsysprof/sysprof-document-traceable.h @@ -0,0 +1,58 @@ +/* + * sysprof-document-traceable.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-document-frame.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT_TRACEABLE (sysprof_document_traceable_get_type ()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_INTERFACE (SysprofDocumentTraceable, sysprof_document_traceable, SYSPROF, DOCUMENT_TRACEABLE, SysprofDocumentFrame) + +struct _SysprofDocumentTraceableInterface +{ + GTypeInterface parent; + + guint (*get_stack_depth) (SysprofDocumentTraceable *self); + guint64 (*get_stack_address) (SysprofDocumentTraceable *self, + guint position); + guint (*get_stack_addresses) (SysprofDocumentTraceable *self, + guint64 *addresses, + guint n_addresses); + int (*get_thread_id) (SysprofDocumentTraceable *self); +}; + +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_traceable_get_stack_depth (SysprofDocumentTraceable *self); +SYSPROF_AVAILABLE_IN_ALL +guint64 sysprof_document_traceable_get_stack_address (SysprofDocumentTraceable *self, + guint position); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_traceable_get_stack_addresses (SysprofDocumentTraceable *self, + guint64 *addresses, + guint n_addresses); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_document_traceable_get_thread_id (SysprofDocumentTraceable *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-document.c b/src/libsysprof/sysprof-document.c new file mode 100644 index 00000000..6984e251 --- /dev/null +++ b/src/libsysprof/sysprof-document.c @@ -0,0 +1,2456 @@ +/* sysprof-document.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include +#include + +#include + +#include "sysprof-document-private.h" + +#include "sysprof-bundled-symbolizer-private.h" +#include "sysprof-callgraph-private.h" +#include "sysprof-cpu-info-private.h" +#include "sysprof-document-bitset-index-private.h" +#include "sysprof-document-counter-private.h" +#include "sysprof-document-ctrdef.h" +#include "sysprof-document-ctrset.h" +#include "sysprof-document-file-chunk.h" +#include "sysprof-document-file-private.h" +#include "sysprof-document-frame-private.h" +#include "sysprof-document-jitmap.h" +#include "sysprof-document-mmap.h" +#include "sysprof-document-overlay.h" +#include "sysprof-document-process-private.h" +#include "sysprof-document-symbols-private.h" +#include "sysprof-mark-catalog-private.h" +#include "sysprof-mount-private.h" +#include "sysprof-mount-device-private.h" +#include "sysprof-mount-namespace-private.h" +#include "sysprof-process-info-private.h" +#include "sysprof-strings-private.h" +#include "sysprof-symbol-private.h" +#include "sysprof-symbolizer-private.h" + +#include "line-reader-private.h" + +#define MAX_STACK_DEPTH 128 + +struct _SysprofDocument +{ + GObject parent_instance; + + SysprofTimeSpan time_span; + + char *title; + + GArray *frames; + GMappedFile *mapped_file; + const guint8 *base; + + GListStore *cpu_info; + + GListStore *counters; + GHashTable *counter_id_to_values; + + SysprofStrings *strings; + + EggBitset *allocations; + EggBitset *file_chunks; + EggBitset *samples; + EggBitset *samples_with_context_switch; + EggBitset *traceables; + EggBitset *processes; + EggBitset *logs; + EggBitset *mmaps; + EggBitset *overlays; + EggBitset *pids; + EggBitset *jitmaps; + EggBitset *ctrdefs; + EggBitset *ctrsets; + EggBitset *marks; + EggBitset *metadata; + + GHashTable *files_first_position; + GHashTable *pid_to_process_info; + GHashTable *tid_to_symbol; + GHashTable *mark_groups; + + SysprofMountNamespace *mount_namespace; + + SysprofDocumentSymbols *symbols; + + SysprofCaptureFileHeader header; + guint needs_swap : 1; +}; + +typedef struct _SysprofDocumentFramePointer +{ + guint64 offset : 48; + guint64 length : 16; +} SysprofDocumentFramePointer; + +enum { + PROP_0, + PROP_ALLOCATIONS, + PROP_COUNTERS, + PROP_CPU_INFO, + PROP_FILES, + PROP_LOGS, + PROP_MARKS, + PROP_METADATA, + PROP_PROCESSES, + PROP_SAMPLES, + PROP_TIME_SPAN, + PROP_TITLE, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static inline guint16 +swap_uint16 (gboolean needs_swap, + guint16 value) +{ + if (!needs_swap) + return value; + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return GUINT16_FROM_BE (value); +#else + return GUINT16_FROM_LE (value); +#endif +} + +static inline guint32 +swap_uint32 (gboolean needs_swap, + guint32 value) +{ + if (!needs_swap) + return value; + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return GUINT32_FROM_BE (value); +#else + return GUINT32_FROM_LE (value); +#endif +} + +static inline int +swap_int32 (gboolean needs_swap, + int value) +{ + if (!needs_swap) + return value; + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return GINT32_FROM_BE (value); +#else + return GINT32_FROM_LE (value); +#endif +} + +static inline gint64 +swap_int64 (gboolean needs_swap, + gint64 value) +{ + if G_LIKELY (!needs_swap) + return value; + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return GINT64_FROM_BE (value); +#else + return GINT64_FROM_LE (value); +#endif +} + +static inline guint64 +swap_uint64 (gboolean needs_swap, + guint64 value) +{ + if G_LIKELY (!needs_swap) + return value; + +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + return GUINT64_FROM_BE (value); +#else + return GUINT64_FROM_LE (value); +#endif +} + +static GType +sysprof_document_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_DOCUMENT_FRAME; +} + +static guint +sysprof_document_get_n_items (GListModel *model) +{ + return SYSPROF_DOCUMENT (model)->frames->len; +} + +static gpointer +sysprof_document_get_item (GListModel *model, + guint position) +{ + SysprofDocument *self = SYSPROF_DOCUMENT (model); + SysprofDocumentFramePointer *ptr; + SysprofDocumentFrame *ret; + + if (position >= self->frames->len) + return NULL; + + ptr = &g_array_index (self->frames, SysprofDocumentFramePointer, position); + ret = _sysprof_document_frame_new (self->mapped_file, + (gconstpointer)&self->base[ptr->offset], + ptr->length, + self->needs_swap, + self->header.time, + self->header.end_time); + + /* Annotate processes with pre-calculated info */ + if (SYSPROF_IS_DOCUMENT_PROCESS (ret)) + { + int pid = sysprof_document_frame_get_pid (ret); + SysprofProcessInfo *process_info = g_hash_table_lookup (self->pid_to_process_info, GINT_TO_POINTER (pid)); + + if (process_info != NULL) + _sysprof_document_process_set_info (SYSPROF_DOCUMENT_PROCESS (ret), process_info); + } + + return ret; +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_document_get_item_type; + iface->get_n_items = sysprof_document_get_n_items; + iface->get_item = sysprof_document_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofDocument, sysprof_document, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +EggBitset * +_sysprof_document_traceables (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return self->traceables; +} + +SysprofProcessInfo * +_sysprof_document_process_info (SysprofDocument *self, + int pid, + gboolean may_create) +{ + SysprofProcessInfo *process_info; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + if (pid < 0) + return NULL; + + process_info = g_hash_table_lookup (self->pid_to_process_info, GINT_TO_POINTER (pid)); + + if (process_info == NULL && may_create) + { + process_info = sysprof_process_info_new (sysprof_mount_namespace_copy (self->mount_namespace), pid); + g_hash_table_insert (self->pid_to_process_info, GINT_TO_POINTER (pid), process_info); + } + + return process_info; +} + +static void +decode_space (gchar **str) +{ + /* Replace encoded space "\040" with ' ' */ + if (strstr (*str, "\\040")) + { + g_auto(GStrv) parts = g_strsplit (*str, "\\040", 0); + g_free (*str); + *str = g_strjoinv (" ", parts); + } +} + +static inline gboolean +has_null_byte (const char *str, + const char *endptr) +{ + for (const char *c = str; c < endptr; c++) + { + if (*c == '\0') + return TRUE; + } + + return FALSE; +} + +static void +sysprof_document_finalize (GObject *object) +{ + SysprofDocument *self = (SysprofDocument *)object; + + g_clear_pointer (&self->strings, sysprof_strings_unref); + + g_clear_pointer (&self->title, g_free); + + g_clear_pointer (&self->pid_to_process_info, g_hash_table_unref); + g_clear_pointer (&self->tid_to_symbol, g_hash_table_unref); + g_clear_pointer (&self->mapped_file, g_mapped_file_unref); + g_clear_pointer (&self->frames, g_array_unref); + + g_clear_pointer (&self->allocations, egg_bitset_unref); + g_clear_pointer (&self->ctrdefs, egg_bitset_unref); + g_clear_pointer (&self->ctrsets, egg_bitset_unref); + g_clear_pointer (&self->marks, egg_bitset_unref); + g_clear_pointer (&self->metadata, egg_bitset_unref); + g_clear_pointer (&self->file_chunks, egg_bitset_unref); + g_clear_pointer (&self->jitmaps, egg_bitset_unref); + g_clear_pointer (&self->logs, egg_bitset_unref); + g_clear_pointer (&self->mmaps, egg_bitset_unref); + g_clear_pointer (&self->overlays, egg_bitset_unref); + g_clear_pointer (&self->pids, egg_bitset_unref); + g_clear_pointer (&self->processes, egg_bitset_unref); + g_clear_pointer (&self->samples, egg_bitset_unref); + g_clear_pointer (&self->samples_with_context_switch, egg_bitset_unref); + g_clear_pointer (&self->traceables, egg_bitset_unref); + + g_clear_pointer (&self->mark_groups, g_hash_table_unref); + + g_clear_object (&self->counters); + g_clear_pointer (&self->counter_id_to_values, g_hash_table_unref); + + g_clear_object (&self->cpu_info); + + g_clear_object (&self->mount_namespace); + g_clear_object (&self->symbols); + + g_clear_pointer (&self->files_first_position, g_hash_table_unref); + + G_OBJECT_CLASS (sysprof_document_parent_class)->finalize (object); +} + +static void +sysprof_document_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDocument *self = SYSPROF_DOCUMENT (object); + + switch (prop_id) + { + case PROP_ALLOCATIONS: + g_value_take_object (value, sysprof_document_list_allocations (self)); + break; + + case PROP_COUNTERS: + g_value_take_object (value, sysprof_document_list_counters (self)); + break; + + case PROP_CPU_INFO: + g_value_take_object (value, sysprof_document_list_cpu_info (self)); + break; + + case PROP_FILES: + g_value_take_object (value, sysprof_document_list_files (self)); + break; + + case PROP_LOGS: + g_value_take_object (value, sysprof_document_list_logs (self)); + break; + + case PROP_MARKS: + g_value_take_object (value, sysprof_document_list_marks (self)); + break; + + case PROP_METADATA: + g_value_take_object (value, sysprof_document_list_metadata (self)); + break; + + case PROP_PROCESSES: + g_value_take_object (value, sysprof_document_list_processes (self)); + break; + + case PROP_SAMPLES: + g_value_take_object (value, sysprof_document_list_samples (self)); + break; + + case PROP_TIME_SPAN: + g_value_set_boxed (value, sysprof_document_get_time_span (self)); + break; + + case PROP_TITLE: + g_value_take_string (value, sysprof_document_dup_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_document_class_init (SysprofDocumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_document_finalize; + object_class->get_property = sysprof_document_get_property; + + properties [PROP_ALLOCATIONS] = + g_param_spec_object ("allocations", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_COUNTERS] = + g_param_spec_object ("counters", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_CPU_INFO] = + g_param_spec_object ("cpu-info", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILES] = + g_param_spec_object ("files", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_LOGS] = + g_param_spec_object ("logs", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MARKS] = + g_param_spec_object ("marks", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_METADATA] = + g_param_spec_object ("metadata", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_PROCESSES] = + g_param_spec_object ("processes", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SAMPLES] = + g_param_spec_object ("samples", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TIME_SPAN] = + g_param_spec_boxed ("time-span", NULL, NULL, + SYSPROF_TYPE_TIME_SPAN, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_document_init (SysprofDocument *self) +{ + self->strings = sysprof_strings_new (); + + self->frames = g_array_new (FALSE, FALSE, sizeof (SysprofDocumentFramePointer)); + + self->cpu_info = g_list_store_new (SYSPROF_TYPE_CPU_INFO); + + self->counters = g_list_store_new (SYSPROF_TYPE_DOCUMENT_COUNTER); + self->counter_id_to_values = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify)g_array_unref); + + self->allocations = egg_bitset_new_empty (); + self->ctrdefs = egg_bitset_new_empty (); + self->ctrsets = egg_bitset_new_empty (); + self->marks = egg_bitset_new_empty (); + self->metadata = egg_bitset_new_empty (); + self->file_chunks = egg_bitset_new_empty (); + self->jitmaps = egg_bitset_new_empty (); + self->logs = egg_bitset_new_empty (); + self->mmaps = egg_bitset_new_empty (); + self->overlays = egg_bitset_new_empty (); + self->pids = egg_bitset_new_empty (); + self->processes = egg_bitset_new_empty (); + self->samples = egg_bitset_new_empty (); + self->samples_with_context_switch = egg_bitset_new_empty (); + self->traceables = egg_bitset_new_empty (); + + self->files_first_position = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + self->pid_to_process_info = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)sysprof_process_info_unref); + self->tid_to_symbol = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref); + self->mark_groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_unref); + + self->mount_namespace = sysprof_mount_namespace_new (); +} + +static void +sysprof_document_load_memory_maps (SysprofDocument *self) +{ + EggBitsetIter iter; + guint i; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + if (egg_bitset_iter_init_first (&iter, self->mmaps, &i)) + { + do + { + g_autoptr(SysprofDocumentMmap) map = sysprof_document_get_item ((GListModel *)self, i); + int pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (map)); + SysprofProcessInfo *process_info = _sysprof_document_process_info (self, pid, TRUE); + + sysprof_address_layout_take (process_info->address_layout, g_steal_pointer (&map)); + } + while (egg_bitset_iter_next (&iter, &i)); + } +} + +static void +sysprof_document_load_mounts (SysprofDocument *self) +{ + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(GBytes) bytes = NULL; + LineReader reader; + const char *contents; + gsize contents_len; + gsize line_len; + char *line; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + if (!(file = sysprof_document_lookup_file (self, "/proc/mounts")) || + !(bytes = sysprof_document_file_dup_bytes (file))) + return; + + contents = g_bytes_get_data (bytes, &contents_len); + + g_assert (contents != NULL); + g_assert (contents[contents_len] == 0); + + line_reader_init (&reader, (char *)contents, contents_len); + while ((line = line_reader_next (&reader, &line_len))) + { + g_autofree char *subvol = NULL; + g_auto(GStrv) parts = NULL; + const char *filesystem; + const char *mountpoint; + const char *device; + const char *options; + + line[line_len] = 0; + + parts = g_strsplit (line, " ", 5); + + /* Field 0: device + * Field 1: mountpoint + * Field 2: filesystem + * Field 3: Options + * .. Ignored .. + */ + if (g_strv_length (parts) != 5) + continue; + + for (guint j = 0; parts[j]; j++) + decode_space (&parts[j]); + + device = parts[0]; + mountpoint = parts[1]; + filesystem = parts[2]; + options = parts[3]; + + if (g_strcmp0 (filesystem, "btrfs") == 0) + { + g_auto(GStrv) opts = g_strsplit (options, ",", 0); + + for (guint k = 0; opts[k]; k++) + { + if (g_str_has_prefix (opts[k], "subvol=")) + { + subvol = g_strdup (opts[k] + strlen ("subvol=")); + break; + } + } + } + + sysprof_mount_namespace_add_device (self->mount_namespace, + sysprof_mount_device_new (sysprof_strings_get (self->strings, device), + sysprof_strings_get (self->strings, mountpoint), + sysprof_strings_get (self->strings, subvol))); + } +} + +static void +sysprof_document_load_mountinfo (SysprofDocument *self, + int pid, + GBytes *bytes) +{ + SysprofProcessInfo *process_info; + const char *contents; + LineReader reader; + gsize contents_len; + gsize line_len; + char *line; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + g_assert (bytes != NULL); + + contents = g_bytes_get_data (bytes, &contents_len); + + g_assert (contents != NULL); + g_assert (contents[contents_len] == 0); + + process_info = _sysprof_document_process_info (self, pid, TRUE); + + g_assert (process_info != NULL); + g_assert (process_info->mount_namespace != NULL); + + line_reader_init (&reader, (char *)contents, contents_len); + while ((line = line_reader_next (&reader, &line_len))) + { + g_autoptr(SysprofMount) mount = NULL; + + line[line_len] = 0; + + if ((mount = _sysprof_mount_new_for_mountinfo (self->strings, line))) + sysprof_mount_namespace_add_mount (process_info->mount_namespace, g_steal_pointer (&mount)); + } +} + +static void +sysprof_document_load_mountinfos (SysprofDocument *self) +{ + EggBitsetIter iter; + guint pid; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + if (egg_bitset_iter_init_first (&iter, self->pids, &pid)) + { + do + { + g_autofree char *path = g_strdup_printf ("/proc/%u/mountinfo", pid); + g_autoptr(SysprofDocumentFile) file = sysprof_document_lookup_file (self, path); + + if (file != NULL) + { + g_autoptr(GBytes) bytes = sysprof_document_file_dup_bytes (file); + + sysprof_document_load_mountinfo (self, pid, bytes); + } + } + while (egg_bitset_iter_next (&iter, &pid)); + } +} + +static void +sysprof_document_load_overlays (SysprofDocument *self) +{ + EggBitsetIter iter; + guint i; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + if (egg_bitset_iter_init_first (&iter, self->overlays, &i)) + { + do + { + g_autoptr(SysprofDocumentOverlay) overlay = g_list_model_get_item (G_LIST_MODEL (self), i); + int pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (overlay)); + SysprofProcessInfo *process_info = _sysprof_document_process_info (self, pid, TRUE); + + if (process_info != NULL) + { + const char *mount_point = sysprof_document_overlay_get_destination (overlay); + const char *host_path = sysprof_document_overlay_get_source (overlay); + int layer = sysprof_document_overlay_get_layer (overlay); + g_autoptr(SysprofMount) mount = _sysprof_mount_new_for_overlay (self->strings, mount_point, host_path, layer); + + sysprof_mount_namespace_add_mount (process_info->mount_namespace, + g_steal_pointer (&mount)); + } + } + while (egg_bitset_iter_next (&iter, &i)); + } +} + +static void +sysprof_document_load_processes (SysprofDocument *self) +{ + EggBitsetIter iter; + guint i; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + if (egg_bitset_iter_init_first (&iter, self->processes, &i)) + { + do + { + g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (G_LIST_MODEL (self), i); + int pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (process)); + SysprofProcessInfo *process_info = _sysprof_document_process_info (self, pid, TRUE); + const char *cmdline = sysprof_document_process_get_command_line (process); + + if (cmdline != NULL) + { + g_auto(GStrv) parts = NULL; + + if ((parts = g_strsplit (cmdline , " ", 2)) && parts[0]) + { + GRefString *nick = g_ref_string_acquire (process_info->fallback_symbol->binary_nick); + + g_clear_object (&process_info->symbol); + process_info->symbol = + _sysprof_symbol_new (sysprof_strings_get (self->strings, parts[0]), + NULL, g_steal_pointer (&nick), 0, 0, + SYSPROF_SYMBOL_KIND_PROCESS); + } + } + } + while (egg_bitset_iter_next (&iter, &i)); + } +} + +static void +sysprof_document_load_counters (SysprofDocument *self) +{ + g_autoptr(GPtrArray) counters = NULL; + g_autoptr(EggBitset) swap_ids = NULL; + GListModel *model; + EggBitsetIter iter; + guint n_counters; + guint i; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + /* Cast up front to avoid many type checks later */ + model = G_LIST_MODEL (self); + + /* If we need to swap int64 (double does not need swapping), then keep + * a bitset of which counters need swapping. That way we have a fast + * lookup below we can use when reading in counter values. + */ + if (self->needs_swap) + swap_ids = egg_bitset_new_empty (); + + /* First create our counter objects which we will use to hold values that + * were extracted from the capture file. We create the array first so that + * we can use g_list_store_splice() rather than have signal emission for + * each and every added counter. + */ + counters = g_ptr_array_new_with_free_func (g_object_unref); + if (egg_bitset_iter_init_first (&iter, self->ctrdefs, &i)) + { + do + { + g_autoptr(SysprofDocumentCtrdef) ctrdef = g_list_model_get_item (model, i); + + n_counters = sysprof_document_ctrdef_get_n_counters (ctrdef); + + for (guint j = 0; j < n_counters; j++) + { + g_autoptr(GArray) values = g_array_new (FALSE, FALSE, sizeof (SysprofDocumentTimedValue)); + const char *category; + const char *name; + const char *description; + guint id; + guint type; + + sysprof_document_ctrdef_get_counter (ctrdef, j, &id, &type, &category, &name, &description); + + /* Keep track if this counter will need int64 endian swaps */ + if (swap_ids != NULL && type == SYSPROF_CAPTURE_COUNTER_INT64) + egg_bitset_add (swap_ids, id); + + g_hash_table_insert (self->counter_id_to_values, + GUINT_TO_POINTER (id), + g_array_ref (values)); + + g_ptr_array_add (counters, + _sysprof_document_counter_new (id, + type, + sysprof_strings_get (self->strings, category), + sysprof_strings_get (self->strings, name), + sysprof_strings_get (self->strings, description), + g_steal_pointer (&values), + self->time_span.begin_nsec)); + } + } + while (egg_bitset_iter_next (&iter, &i)); + + if (counters->len > 0) + g_list_store_splice (self->counters, 0, 0, counters->pdata, counters->len); + } + + /* Now find all the counter values and associate them with the counters + * that were previously defined. + */ + if (egg_bitset_iter_init_first (&iter, self->ctrsets, &i)) + { + do + { + g_autoptr(SysprofDocumentCtrset) ctrset = g_list_model_get_item (model, i); + guint n_values = sysprof_document_ctrset_get_n_values (ctrset); + SysprofDocumentTimedValue ctrval; + + ctrval.time = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (ctrset)); + + for (guint j = 0; j < n_values; j++) + { + GArray *values; + guint id; + + sysprof_document_ctrset_get_raw_value (ctrset, j, &id, ctrval.v_raw); + + if (swap_ids != NULL && egg_bitset_contains (swap_ids, id)) + { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + ctrval.v_int64 = GINT64_FROM_BE (ctrval.v_int64); +#else + ctrval.v_int64 = GINT64_FROM_LE (ctrval.v_int64); +#endif + } + + if ((values = g_hash_table_lookup (self->counter_id_to_values, GUINT_TO_POINTER (id)))) + g_array_append_val (values, ctrval); + } + } + while (egg_bitset_iter_next (&iter, &i)); + } + + n_counters = g_list_model_get_n_items (G_LIST_MODEL (self->counters)); + + for (i = 0; i < n_counters; i++) + { + g_autoptr(SysprofDocumentCounter) counter = g_list_model_get_item (G_LIST_MODEL (self->counters), i); + + _sysprof_document_counter_calculate_range (counter); + } +} + +static inline gboolean +is_data_type (SysprofCaptureFrameType type) +{ + switch (type) + { + case SYSPROF_CAPTURE_FRAME_ALLOCATION: + case SYSPROF_CAPTURE_FRAME_CTRSET: + case SYSPROF_CAPTURE_FRAME_EXIT: + case SYSPROF_CAPTURE_FRAME_FORK: + case SYSPROF_CAPTURE_FRAME_LOG: + case SYSPROF_CAPTURE_FRAME_MAP: + case SYSPROF_CAPTURE_FRAME_MARK: + case SYSPROF_CAPTURE_FRAME_PROCESS: + case SYSPROF_CAPTURE_FRAME_SAMPLE: + case SYSPROF_CAPTURE_FRAME_TRACE: + return TRUE; + + case SYSPROF_CAPTURE_FRAME_CTRDEF: + case SYSPROF_CAPTURE_FRAME_FILE_CHUNK: + case SYSPROF_CAPTURE_FRAME_JITMAP: + case SYSPROF_CAPTURE_FRAME_METADATA: + case SYSPROF_CAPTURE_FRAME_OVERLAY: + case SYSPROF_CAPTURE_FRAME_TIMESTAMP: + default: + return FALSE; + } +} + +typedef struct _Load +{ + GMappedFile *mapped_file; + ProgressFunc progress; + gpointer progress_data; + GDestroyNotify progress_data_destroy; +} Load; + +static void +load_free (Load *load) +{ + g_clear_pointer (&load->mapped_file, g_mapped_file_unref); + + if (load->progress_data_destroy) + load->progress_data_destroy (load->progress_data); + + load->progress = NULL; + load->progress_data = NULL; + load->progress_data_destroy = NULL; + + g_free (load); +} + +static inline void +load_progress (Load *load, + double fraction, + const char *message) +{ + if (load->progress) + load->progress (fraction, message, load->progress_data); +} + +static int +sort_by_time (gconstpointer a, + gconstpointer b, + gpointer base) +{ + const SysprofDocumentFramePointer *aptr = a; + const SysprofDocumentFramePointer *bptr = b; + const SysprofCaptureFrame *aframe = (const SysprofCaptureFrame *)((const guint8 *)base + aptr->offset); + const SysprofCaptureFrame *bframe = (const SysprofCaptureFrame *)((const guint8 *)base + bptr->offset); + + if (aframe->time < bframe->time) + return -1; + + if (aframe->time > bframe->time) + return 1; + + if (aframe->type == SYSPROF_CAPTURE_FRAME_MARK && bframe->type == SYSPROF_CAPTURE_FRAME_MARK) + { + const SysprofCaptureMark *amark = (const SysprofCaptureMark *)aframe; + const SysprofCaptureMark *bmark = (const SysprofCaptureMark *)bframe; + + if (amark->duration > bmark->duration) + return -1; + else if (amark->duration < bmark->duration) + return 1; + } + + return 0; +} + +static int +sort_by_time_swapped (gconstpointer a, + gconstpointer b, + gpointer base) +{ + const SysprofDocumentFramePointer *aptr = a; + const SysprofDocumentFramePointer *bptr = b; + const SysprofCaptureFrame *aframe = (const SysprofCaptureFrame *)((const guint8 *)base + aptr->offset); + const SysprofCaptureFrame *bframe = (const SysprofCaptureFrame *)((const guint8 *)base + bptr->offset); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + gint64 atime = GINT64_FROM_BE (aframe->time); + gint64 btime = GINT64_FROM_BE (bframe->time); +#else + gint64 atime = GINT64_FROM_LE (aframe->time); + gint64 btime = GINT64_FROM_LE (bframe->time); +#endif + + if (atime < btime) + return -1; + + if (atime > btime) + return 1; + + if (aframe->type == SYSPROF_CAPTURE_FRAME_MARK && bframe->type == SYSPROF_CAPTURE_FRAME_MARK) + { + const SysprofCaptureMark *amark = (const SysprofCaptureMark *)aframe; + const SysprofCaptureMark *bmark = (const SysprofCaptureMark *)bframe; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + gint64 aduration = GINT64_FROM_BE (amark->duration); + gint64 bduration = GINT64_FROM_BE (bmark->duration); +#else + gint64 aduration = GINT64_FROM_LE (amark->duration); + gint64 bduration = GINT64_FROM_LE (bmark->duration); +#endif + + if (aduration > bduration) + return -1; + else if (aduration < bduration) + return 1; + } + + return 0; +} + +static void +sysprof_document_update_process_exit_times (SysprofDocument *self) +{ + GHashTableIter iter; + SysprofProcessInfo *info; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + g_hash_table_iter_init (&iter, self->pid_to_process_info); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&info)) + { + if (info->exit_time == 0) + info->exit_time = self->time_span.end_nsec; + } +} + +static void +sysprof_document_load_cpu (SysprofDocument *self) +{ + const gsize model_len = strlen ("Model\t\t: "); + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(SysprofCpuInfo) cpu_info = NULL; + g_autofree char *model = NULL; + const char *str; + const char *line; + LineReader reader; + gsize line_len; + gsize len; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + if (!(file = sysprof_document_lookup_file (self, "/proc/cpuinfo")) || + !(bytes = sysprof_document_file_dup_bytes (file))) + return; + + str = (const char *)g_bytes_get_data (bytes, &len); + + line_reader_init (&reader, (char *)str, len); + while ((line = line_reader_next (&reader, &line_len))) + { + if (g_str_has_prefix (line, "processor\t: ")) + { + gint64 id = g_ascii_strtoll (line+strlen("processor\t: "), NULL, 10); + + if (cpu_info != NULL) + g_list_store_append (self->cpu_info, cpu_info); + + g_clear_object (&cpu_info); + + cpu_info = g_object_new (SYSPROF_TYPE_CPU_INFO, + "id", id, + NULL); + } + + if (g_str_has_prefix (line, "core id\t\t: ")) + { + gint64 core_id = g_ascii_strtoll (line+strlen("core id\t\t: "), NULL, 10); + + if (core_id > 0) + _sysprof_cpu_info_set_core_id (cpu_info, core_id); + } + + if (g_str_has_prefix (line, "model name\t: ")) + { + const gsize model_name_len = strlen ("model name\t: "); + g_autofree char *model_name = g_strndup (line+model_name_len, line_len-model_name_len); + + if (cpu_info != NULL) + _sysprof_cpu_info_set_model_name (cpu_info, model_name); + } + + if (!model && g_str_has_prefix (line, "Model\t\t: ")) + model = g_strndup (line+model_len, line_len-model_len); + } + + if (cpu_info != NULL) + g_list_store_append (self->cpu_info, cpu_info); + + if (model != NULL) + { + guint n_items = g_list_model_get_n_items (G_LIST_MODEL (self->cpu_info)); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofCpuInfo) item = g_list_model_get_item (G_LIST_MODEL (self->cpu_info), i); + + _sysprof_cpu_info_set_model_name (item, model); + } + } +} + +static void +sysprof_document_load_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr(SysprofDocument) self = NULL; + g_autoptr(GHashTable) files = NULL; + Load *load = task_data; + gint64 guessed_end_nsec = 0; + goffset pos; + gsize len; + guint count; + + g_assert (source_object == NULL); + g_assert (load != NULL); + + self = g_object_new (SYSPROF_TYPE_DOCUMENT, NULL); + self->mapped_file = g_mapped_file_ref (load->mapped_file); + self->base = (const guint8 *)g_mapped_file_get_contents (load->mapped_file); + len = g_mapped_file_get_length (load->mapped_file); + + if (len < sizeof self->header) + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_INVALID_DATA, + "File header too short"); + return; + } + + /* Keep a copy of our header */ + memcpy (&self->header, self->base, sizeof self->header); +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + self->needs_swap = !self->header.little_endian; +#else + self->needs_swap = !!self->header.little_endian; +#endif + + self->header.time = swap_uint64 (self->needs_swap, self->header.time); + self->header.end_time = swap_uint64 (self->needs_swap, self->header.end_time); + self->header.capture_time[sizeof self->header.capture_time-1] = 0; + + self->time_span.begin_nsec = self->header.time; + self->time_span.end_nsec = self->header.end_time; + + load_progress (load, .1, _("Indexing capture data frames")); + + count = 0; + pos = sizeof self->header; + while (pos < (len - sizeof(guint16))) + { + SysprofDocumentFramePointer ptr; + guint16 frame_len; + + memcpy (&frame_len, &self->base[pos], sizeof frame_len); + frame_len = swap_uint16 (self->needs_swap, frame_len); + + if (frame_len < sizeof (SysprofCaptureFrame)) + { + g_warning ("Capture contained implausibly short frame"); + break; + } + + if (frame_len % SYSPROF_CAPTURE_ALIGN != 0) + { + g_warning ("Capture contained frame not aligned to %u", + (guint)SYSPROF_CAPTURE_ALIGN); + break; + } + + ptr.offset = pos; + ptr.length = frame_len; + + pos += frame_len; + count++; + + g_array_append_val (self->frames, ptr); + + if (count % 100 == 0) + load_progress (load, + (pos / (double)len) * .4, + _("Indexing capture data frames")); + } + + /* Now sort all the items by their respective frame times as frames + * cat into the writer may be tacked on at the end even though they + * have state which belongs earlier in the capture. + */ + if G_UNLIKELY (self->needs_swap) + g_array_sort_with_data (self->frames, sort_by_time_swapped, (gpointer)self->base); + else + g_array_sort_with_data (self->frames, sort_by_time, (gpointer)self->base); + + for (guint f = 0; f < self->frames->len; f++) + { + SysprofDocumentFramePointer *ptr = &g_array_index (self->frames, SysprofDocumentFramePointer, f); + const SysprofCaptureFrame *tainted = (const SysprofCaptureFrame *)(gpointer)&self->base[ptr->offset]; + gint64 t = swap_int64 (self->needs_swap, tainted->time); + int pid = swap_int32 (self->needs_swap, tainted->pid); + + if (t > guessed_end_nsec && is_data_type (tainted->type)) + guessed_end_nsec = t; + + egg_bitset_add (self->pids, pid); + + switch ((int)tainted->type) + { + case SYSPROF_CAPTURE_FRAME_ALLOCATION: + egg_bitset_add (self->allocations, f); + egg_bitset_add (self->traceables, f); + break; + + case SYSPROF_CAPTURE_FRAME_SAMPLE: + egg_bitset_add (self->samples, f); + egg_bitset_add (self->traceables, f); + break; + + case SYSPROF_CAPTURE_FRAME_PROCESS: + egg_bitset_add (self->processes, f); + break; + + case SYSPROF_CAPTURE_FRAME_FILE_CHUNK: + egg_bitset_add (self->file_chunks, f); + break; + + case SYSPROF_CAPTURE_FRAME_CTRDEF: + egg_bitset_add (self->ctrdefs, f); + break; + + case SYSPROF_CAPTURE_FRAME_CTRSET: + egg_bitset_add (self->ctrsets, f); + break; + + case SYSPROF_CAPTURE_FRAME_MARK: + egg_bitset_add (self->marks, f); + break; + + case SYSPROF_CAPTURE_FRAME_METADATA: + egg_bitset_add (self->metadata, f); + break; + + case SYSPROF_CAPTURE_FRAME_JITMAP: + egg_bitset_add (self->jitmaps, f); + break; + + case SYSPROF_CAPTURE_FRAME_LOG: + egg_bitset_add (self->logs, f); + break; + + case SYSPROF_CAPTURE_FRAME_MAP: + egg_bitset_add (self->mmaps, f); + break; + + case SYSPROF_CAPTURE_FRAME_OVERLAY: + egg_bitset_add (self->overlays, f); + break; + + default: + break; + } + + if (tainted->type == SYSPROF_CAPTURE_FRAME_FILE_CHUNK) + { + const SysprofCaptureFileChunk *file_chunk = (const SysprofCaptureFileChunk *)tainted; + + if (has_null_byte (file_chunk->path, (const char *)file_chunk->data) && + !g_hash_table_contains (self->files_first_position, file_chunk->path)) + g_hash_table_insert (self->files_first_position, + g_strdup (file_chunk->path), + GUINT_TO_POINTER (f)); + } + else if (tainted->type == SYSPROF_CAPTURE_FRAME_SAMPLE) + { + const SysprofCaptureSample *sample = (const SysprofCaptureSample *)tainted; + guint n_addrs = swap_uint16 (self->needs_swap, sample->n_addrs); + const guint8 *endptr = (const guint8 *)tainted + ptr->length; + + /* If the sample contains a context-switch, record it */ + if ((const guint8 *)sample + (n_addrs * sizeof (SysprofAddress)) <= endptr) + { + SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_USER; + + for (guint i = 0; i < n_addrs; i++) + { + SysprofAddress addr = swap_uint64 (self->needs_swap, sample->addrs[i]); + + if (sysprof_address_is_context_switch (addr, &last_context) && + last_context == SYSPROF_ADDRESS_CONTEXT_KERNEL) + { + egg_bitset_add (self->samples_with_context_switch, f); + break; + } + } + } + + if (sample->tid != tainted->pid) + { + SysprofProcessInfo *info = _sysprof_document_process_info (self, pid, TRUE); + sysprof_process_info_seen_thread (info, swap_int32 (self->needs_swap, sample->tid)); + } + } + else if (tainted->type == SYSPROF_CAPTURE_FRAME_ALLOCATION) + { + const SysprofCaptureAllocation *alloc = (const SysprofCaptureAllocation *)tainted; + SysprofProcessInfo *info = _sysprof_document_process_info (self, pid, TRUE); + sysprof_process_info_seen_thread (info, swap_int32 (self->needs_swap, alloc->tid)); + } + else if (tainted->type == SYSPROF_CAPTURE_FRAME_EXIT) + { + SysprofProcessInfo *info = _sysprof_document_process_info (self, pid, TRUE); + + info->exit_time = t; + } + else if (tainted->type == SYSPROF_CAPTURE_FRAME_MARK) + { + const SysprofCaptureMark *mark = (const SysprofCaptureMark *)tainted; + const char *endptr = (const char *)tainted + ptr->length; + gint64 duration = swap_int64 (self->needs_swap, mark->duration); + + if (t + duration > guessed_end_nsec) + guessed_end_nsec = t + duration; + + if (has_null_byte (mark->group, endptr) && + has_null_byte (mark->name, endptr)) + { + const char *group = mark->group; + const char *name = mark->name; + GHashTable *names; + EggBitset *bitset; + + if (!(names = g_hash_table_lookup (self->mark_groups, group))) + { + names = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify)egg_bitset_unref); + g_hash_table_insert (self->mark_groups, g_strdup (group), names); + } + + if (!(bitset = g_hash_table_lookup (names, name))) + { + bitset = egg_bitset_new_empty (); + g_hash_table_insert (names, g_strdup (name), bitset); + } + + egg_bitset_add (bitset, f); + } + } + } + + if (guessed_end_nsec > self->time_span.begin_nsec) + self->time_span.end_nsec = guessed_end_nsec; + + sysprof_document_load_cpu (self); + + load_progress (load, .6, _("Discovering file system mounts")); + sysprof_document_load_mounts (self); + + load_progress (load, .65, _("Discovering process mount namespaces")); + sysprof_document_load_mountinfos (self); + + load_progress (load, .7, _("Analyzing process address layouts")); + sysprof_document_load_memory_maps (self); + + load_progress (load, .75, _("Analyzing process command line")); + sysprof_document_load_processes (self); + + load_progress (load, .8, _("Analyzing file system overlays")); + sysprof_document_load_overlays (self); + + load_progress (load, .85, _("Processing counters")); + sysprof_document_load_counters (self); + + /* Ensure all our process have an exit_time set */ + sysprof_document_update_process_exit_times (self); + + g_task_return_pointer (task, g_steal_pointer (&self), g_object_unref); +} + +void +_sysprof_document_new_async (GMappedFile *mapped_file, + ProgressFunc progress, + gpointer progress_data, + GDestroyNotify progress_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + Load *load; + + g_return_if_fail (mapped_file != NULL); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + load = g_new0 (Load, 1); + load->mapped_file = g_mapped_file_ref (mapped_file); + load->progress = progress; + load->progress_data = progress_data; + load->progress_data_destroy = progress_data_destroy; + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, _sysprof_document_new_async); + g_task_set_task_data (task, load, (GDestroyNotify)load_free); + g_task_run_in_thread (task, sysprof_document_load_worker); +} + +SysprofDocument * +_sysprof_document_new_finish (GAsyncResult *result, + GError **error) +{ + SysprofDocument *ret; + + g_return_val_if_fail (G_IS_TASK (result), NULL); + ret = g_task_propagate_pointer (G_TASK (result), error); + g_return_val_if_fail (!ret || SYSPROF_IS_DOCUMENT (ret), NULL); + + return ret; +} + +GRefString * +_sysprof_document_ref_string (SysprofDocument *self, + const char *name) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return sysprof_strings_get (self->strings, name); +} + +static void +sysprof_document_symbolize_symbols_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(SysprofDocumentSymbols) symbols = NULL; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + SysprofDocument *self; + + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + if (!(symbols = _sysprof_document_symbols_new_finish (result, &error))) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + self = g_task_get_source_object (task); + + g_assert (self != NULL); + g_assert (SYSPROF_IS_DOCUMENT (self)); + + g_set_object (&self->symbols, symbols); + + g_task_return_boolean (task, TRUE); +} + +typedef struct _Symbolize +{ + ProgressFunc progress_func; + gpointer progress_data; + GDestroyNotify progress_data_destroy; +} Symbolize; + +static void +symbolize_free (Symbolize *state) +{ + if (state->progress_data_destroy) + state->progress_data_destroy (state->progress_data); + g_free (state); +} + +static void +sysprof_document_symbolize_prepare_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofSymbolizer *symbolizer = (SysprofSymbolizer *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + SysprofDocument *self; + Symbolize *state; + + g_assert (SYSPROF_IS_SYMBOLIZER (symbolizer)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + self = g_task_get_source_object (task); + + g_assert (self != NULL); + g_assert (SYSPROF_IS_DOCUMENT (self)); + g_assert (self->pid_to_process_info != NULL); + + state = g_task_get_task_data (task); + + if (!_sysprof_symbolizer_prepare_finish (symbolizer, result, &error)) + g_task_return_error (task, g_steal_pointer (&error)); + else + _sysprof_document_symbols_new (g_task_get_source_object (task), + self->strings, + symbolizer, + self->pid_to_process_info, + state->progress_func, + state->progress_data, + NULL, + g_task_get_cancellable (task), + sysprof_document_symbolize_symbols_cb, + g_object_ref (task)); +} + +void +_sysprof_document_symbolize_async (SysprofDocument *self, + SysprofSymbolizer *symbolizer, + ProgressFunc progress_func, + gpointer progress_data, + GDestroyNotify progress_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + Symbolize *state; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (self)); + g_return_if_fail (SYSPROF_IS_SYMBOLIZER (symbolizer)); + g_return_if_fail (!cancellable || SYSPROF_IS_SYMBOLIZER (symbolizer)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, _sysprof_document_symbolize_async); + + state = g_new0 (Symbolize, 1); + state->progress_func = progress_func; + state->progress_data = progress_data; + state->progress_data_destroy = progress_data_destroy; + g_task_set_task_data(task, state, (GDestroyNotify)symbolize_free); + + _sysprof_symbolizer_prepare_async (symbolizer, + self, + cancellable, + sysprof_document_symbolize_prepare_cb, + g_steal_pointer (&task)); +} + +gboolean +_sysprof_document_symbolize_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), FALSE); + g_return_val_if_fail (G_IS_TASK (result), FALSE); + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == _sysprof_document_symbolize_async, FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +gboolean +_sysprof_document_is_native (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), FALSE); + + return self->needs_swap == FALSE; +} + +/** + * sysprof_document_lookup_file: + * @self: a #SysprofDocument + * @path: the path of the file + * + * Locates @path within the document and returns a #SysprofDocumentFile if + * it was found which can be used to access the contents. + * + * Returns: (transfer full) (nullable): a #SysprofDocumentFile + */ +SysprofDocumentFile * +sysprof_document_lookup_file (SysprofDocument *self, + const char *path) +{ + g_autofree char *gz_path = NULL; + gpointer key, value; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (path != NULL, NULL); + + gz_path = g_strdup_printf ("%s.gz", path); + + if (g_hash_table_lookup_extended (self->files_first_position, path, &key, &value) || + g_hash_table_lookup_extended (self->files_first_position, gz_path, &key, &value)) + { + g_autoptr(GPtrArray) file_chunks = g_ptr_array_new_with_free_func (g_object_unref); + const char *real_path = key; + EggBitsetIter iter; + guint target = GPOINTER_TO_SIZE (value); + guint i; + + if (egg_bitset_iter_init_at (&iter, self->file_chunks, target, &i)) + { + do + { + g_autoptr(SysprofDocumentFileChunk) file_chunk = sysprof_document_get_item ((GListModel *)self, i); + + if (g_strcmp0 (real_path, sysprof_document_file_chunk_get_path (file_chunk)) == 0) + { + gboolean is_last = sysprof_document_file_chunk_get_is_last (file_chunk); + + g_ptr_array_add (file_chunks, g_steal_pointer (&file_chunk)); + + if (is_last) + break; + } + } + while (egg_bitset_iter_next (&iter, &i)); + } + + return _sysprof_document_file_new (path, + g_steal_pointer (&file_chunks), + g_strcmp0 (real_path, gz_path) == 0); + } + + return NULL; +} + +/** + * sysprof_document_list_files: + * @self: a #SysprofDocument + * + * Gets a #GListModel of #SysprofDocumentFile + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentFile + */ +GListModel * +sysprof_document_list_files (SysprofDocument *self) +{ + GHashTableIter hiter; + EggBitsetIter iter; + GListStore *model; + gpointer key, value; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + model = g_list_store_new (SYSPROF_TYPE_DOCUMENT_FILE); + + g_hash_table_iter_init (&hiter, self->files_first_position); + while (g_hash_table_iter_next (&hiter, &key, &value)) + { + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(GPtrArray) file_chunks = g_ptr_array_new_with_free_func (g_object_unref); + g_autofree char *no_gz_path = NULL; + const char *path = key; + guint target = GPOINTER_TO_SIZE (value); + guint i; + + if (egg_bitset_iter_init_at (&iter, self->file_chunks, target, &i)) + { + do + { + g_autoptr(SysprofDocumentFileChunk) file_chunk = sysprof_document_get_item ((GListModel *)self, i); + + if (g_strcmp0 (path, sysprof_document_file_chunk_get_path (file_chunk)) == 0) + { + gboolean is_last = sysprof_document_file_chunk_get_is_last (file_chunk); + + g_ptr_array_add (file_chunks, g_steal_pointer (&file_chunk)); + + if (is_last) + break; + } + } + while (egg_bitset_iter_next (&iter, &i)); + } + + if (g_str_has_suffix (path, ".gz")) + path = no_gz_path = g_strndup (path, strlen (path) - 3); + + file = _sysprof_document_file_new (path, + g_steal_pointer (&file_chunks), + no_gz_path != NULL); + + g_list_store_append (model, file); + } + + return G_LIST_MODEL (model); +} + +/** + * sysprof_document_list_traceables: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentTraceable found within + * the #SysprofDocument. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentTraceable + */ +GListModel * +sysprof_document_list_traceables (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->traceables); +} + +/** + * sysprof_document_list_marks: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentMark found within + * the #SysprofDocument. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentMark + */ +GListModel * +sysprof_document_list_marks (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->marks); +} + +/** + * sysprof_document_list_marks_by_group: + * @self: a #SysprofDocument + * @group: the name of the group + * + * Gets a #GListModel containing #SysprofDocumentMark found within + * the #SysprofDocument which are part of @group. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentMark + */ +GListModel * +sysprof_document_list_marks_by_group (SysprofDocument *self, + const char *group) +{ + g_autoptr(EggBitset) bitset = NULL; + GHashTable *names; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (group != NULL, NULL); + + bitset = egg_bitset_new_empty (); + + if ((names = g_hash_table_lookup (self->mark_groups, group))) + { + GHashTableIter iter; + const char *name; + EggBitset *indices; + + g_hash_table_iter_init (&iter, names); + + while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&indices)) + egg_bitset_union (bitset, indices); + } + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), bitset); +} + +/** + * sysprof_document_list_allocations: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentAllocation found within + * the #SysprofDocument. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentAllocation + */ +GListModel * +sysprof_document_list_allocations (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->allocations); +} + +/** + * sysprof_document_list_samples: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentSample found within + * the #SysprofDocument. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentSample + */ +GListModel * +sysprof_document_list_samples (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->samples); +} + +/** + * sysprof_document_list_samples_with_context_switch: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentSample found within + * the #SysprofDocument which contain a context switch. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentSample + */ +GListModel * +sysprof_document_list_samples_with_context_switch (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->samples_with_context_switch); +} + +/** + * sysprof_document_list_samples_without_context_switch: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentSample found within + * the #SysprofDocument which contain a context switch. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentSample + */ +GListModel * +sysprof_document_list_samples_without_context_switch (SysprofDocument *self) +{ + g_autoptr(EggBitset) bitset = NULL; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + bitset = egg_bitset_copy (self->samples); + egg_bitset_subtract (bitset, self->samples_with_context_switch); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), bitset); +} + +/** + * sysprof_document_list_processes: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentProcess found within + * the #SysprofDocument. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentProcess + */ +GListModel * +sysprof_document_list_processes (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->processes); +} + +/** + * sysprof_document_list_jitmaps: + * @self: a #SysprofDocument + * + * Gets a #GListModel containing #SysprofDocumentJitmap found within + * the #SysprofDocument. + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentJitmap + */ +GListModel * +sysprof_document_list_jitmaps (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->jitmaps); +} + +/** + * sysprof_document_symbolize_traceable: (skip) + * @self: a #SysprofDocument + * @traceable: the traceable to extract symbols for + * @symbols: an array to store #SysprofSymbols + * @n_symbols: the number of elements in @symbols + * @final_context: (out) (nullable): a location to store the last address + * context of the stack trace. + * + * Batch symbolizing of a traceable. + * + * No ownership is transfered into @symbols and may be cheaply + * discarded if using the stack for storage. + * + * Returns: The number of symbols or NULL set in @symbols. + */ +guint +sysprof_document_symbolize_traceable (SysprofDocument *self, + SysprofDocumentTraceable *traceable, + SysprofSymbol **symbols, + guint n_symbols, + SysprofAddressContext *final_context) +{ + SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; + const SysprofProcessInfo *process_info; + SysprofAddress *addresses; + guint n_addresses; + guint n_symbolized = 0; + int pid; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), 0); + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable), 0); + + if (n_symbols == 0 || symbols == NULL) + goto finish; + + pid = sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable)); + process_info = g_hash_table_lookup (self->pid_to_process_info, GINT_TO_POINTER (pid)); + addresses = g_alloca (sizeof (SysprofAddress) * n_symbols); + n_addresses = sysprof_document_traceable_get_stack_addresses (traceable, addresses, n_symbols); + + for (guint i = 0; i < n_addresses; i++) + { + SysprofAddressContext context; + + symbols[n_symbolized] = _sysprof_document_symbols_lookup (self->symbols, process_info, last_context, addresses[i]); + + if (symbols[n_symbolized] != NULL) + n_symbolized++; + + if (sysprof_address_is_context_switch (addresses[i], &context)) + last_context = context; + } + +finish: + if (final_context) + *final_context = last_context; + + return n_symbolized; +} + +/** + * sysprof_document_list_logs: + * @self: a #SysprofDocument + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentLog + */ +GListModel * +sysprof_document_list_logs (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->logs); +} + +/** + * sysprof_document_list_metadata: + * @self: a #SysprofDocument + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentMetadata + */ +GListModel * +sysprof_document_list_metadata (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->metadata); +} + +/** + * sysprof_document_list_counters: + * @self: a #SysprofDocument + * + * Returns: (transfer full): a #GListModel of #SysprofDocumentCounter + */ +GListModel * +sysprof_document_list_counters (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return g_object_ref (G_LIST_MODEL (self->counters)); +} + +static void +sysprof_document_callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(SysprofCallgraph) callgraph = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GTask) task = user_data; + + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + if (!(callgraph = _sysprof_callgraph_new_finish (result, &error))) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_pointer (task, g_steal_pointer (&callgraph), g_object_unref); +} + +/** + * sysprof_document_callgraph_async: + * @self: a #SysprofDocument + * @flags: flags for generating the callgraph + * @traceables: a list model of traceables for the callgraph + * @augment_size: the size of data to reserve for augmentation in + * the callgraph. + * @augment_func: (nullable) (scope notified): an optional callback + * to be executed for each node in the callgraph traces to augment + * as the callgraph is generated. + * @augment_func_data: (closure augment_func) (nullable): the closure + * data for @augment_func + * @augment_func_data_destroy: (destroy augment_func) (nullable): the + * destroy callback for @augment_func_data. + * @cancellable: (nullable): a #GCancellable or %NULL + * @callback: a callback to execute upon completion + * @user_data: closure data for @callback + * + * Asynchronously generates a callgraph using the @traceables to get + * the call stacks. + * + * Ideally you want @augment_size to be <= the size of a pointer to + * avoid extra allocations per callgraph node. + */ +void +sysprof_document_callgraph_async (SysprofDocument *self, + SysprofCallgraphFlags flags, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (self)); + g_return_if_fail (G_IS_LIST_MODEL (traceables)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_document_callgraph_async); + + _sysprof_callgraph_new_async (self, + flags, + traceables, + augment_size, + augment_func, + augment_func_data, + augment_func_data_destroy, + cancellable, + sysprof_document_callgraph_cb, + g_steal_pointer (&task)); +} + +/** + * sysprof_document_callgraph_finish: + * @self: a #SysprofDocument + * @result: the #GAsyncResult provided to callback + * @error: a location for a #GError + * + * Completes a request to generate a callgraph. + * + * Returns: (transfer full): a #SysprofCallgraph if successful; otherwise %NULL + * and @error is set. + */ +SysprofCallgraph * +sysprof_document_callgraph_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +SysprofSymbol * +_sysprof_document_process_symbol (SysprofDocument *self, + int pid) +{ + SysprofProcessInfo *info; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + if (pid < 0) + pid = 0; + + info = _sysprof_document_process_info (self, pid, TRUE); + + if (info->symbol) + return info->symbol; + + return info->fallback_symbol; +} + +SysprofSymbol * +_sysprof_document_thread_symbol (SysprofDocument *self, + int pid, + int tid) +{ + SysprofSymbol *ret; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + if (!(ret = g_hash_table_lookup (self->tid_to_symbol, GINT_TO_POINTER (tid)))) + { + char pidstr[32]; + char tidstr[32]; + + g_snprintf (pidstr, sizeof pidstr, "(%d)", pid); + + if (tid == pid) + g_snprintf (tidstr, sizeof tidstr, "Thread-%d (Main)", tid); + else + g_snprintf (tidstr, sizeof tidstr, "Thread-%d", tid); + + ret = _sysprof_symbol_new (g_ref_string_new (tidstr), + NULL, + g_ref_string_new (pidstr), + 0, 0, + SYSPROF_SYMBOL_KIND_THREAD); + + g_hash_table_insert (self->tid_to_symbol, GINT_TO_POINTER (tid), ret); + } + + return ret; +} + +SysprofSymbol * +_sysprof_document_kernel_symbol (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return self->symbols->context_switches[SYSPROF_ADDRESS_CONTEXT_KERNEL]; +} + +/** + * sysprof_document_get_clock_at_start: + * @self: a #SysprofDocument + * + * Gets the clock time when the recording started. + * + * Returns: the clock time, generally of `CLOCK_MONOTONIC` + */ +gint64 +sysprof_document_get_clock_at_start (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), 0); + + return self->header.time; +} + +/** + * sysprof_document_list_symbols_in_traceable: + * @self: a #SysprofDocument + * @traceable: a #SysprofDocumentTraceable + * + * Gets the symbols in @traceable as a list model. + * + * Returns: (transfer full): a #GListModel of #SysprofSymbol + */ +GListModel * +sysprof_document_list_symbols_in_traceable (SysprofDocument *self, + SysprofDocumentTraceable *traceable) +{ + SysprofAddressContext final_context; + GListStore *ret = NULL; + SysprofSymbol **symbols; + guint stack_depth; + guint n_symbols; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable), NULL); + + ret = g_list_store_new (SYSPROF_TYPE_SYMBOL); + + stack_depth = MIN (MAX_STACK_DEPTH, sysprof_document_traceable_get_stack_depth (traceable)); + symbols = g_alloca (sizeof (SysprofSymbol *) * stack_depth); + n_symbols = sysprof_document_symbolize_traceable (self, traceable, symbols, stack_depth, &final_context); + + if (n_symbols > 0 && _sysprof_symbol_is_context_switch (symbols[0])) + { + symbols++; + n_symbols--; + } + + /* We must make a copy of the symbols because GtkListViewBase does not + * deal with the same object being in a list gracefully. Realistically + * we should deal with this there, but this gets things moving forward + * for the time being. + */ + for (guint i = 0; i < n_symbols; i++) + { + g_autoptr(SysprofSymbol) copy = _sysprof_symbol_copy (symbols[i]); + g_list_store_append (ret, copy); + } + + return G_LIST_MODEL (ret); +} + +static int +str_compare (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + return g_strcmp0 (*(const char * const *)a, *(const char * const *)b); +} + +/** + * sysprof_document_catalog_marks: + * @self: a #SysprofDocument + * + * Get's a #GListModel of #GListModel of #SysprofMarkCatalog. + * + * You can use this to display sections in #GtkListView and similar + * using #GtkFlattenListModel. + * + * Returns: (transfer full): a #GListModel of #GListModel of + * #SysprofMarkCatalog grouped by mark group. + */ +GListModel * +sysprof_document_catalog_marks (SysprofDocument *self) +{ + GListStore *store; + GHashTableIter iter; + gpointer key, value; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + store = g_list_store_new (G_TYPE_LIST_MODEL); + + g_hash_table_iter_init (&iter, self->mark_groups); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + g_autoptr(GListStore) group = NULL; + g_autofree const char **keys = NULL; + const char *group_name = key; + GHashTable *names = value; + guint len; + + group = g_list_store_new (SYSPROF_TYPE_MARK_CATALOG); + + keys = (const char **)g_hash_table_get_keys_as_array (names, &len); + g_qsort_with_data (keys, len, sizeof (char *), (GCompareDataFunc)str_compare, NULL); + + for (guint i = 0; i < len; i++) + { + const char *name = keys[i]; + EggBitset *marks = g_hash_table_lookup (names, name); + g_autoptr(GListModel) model = _sysprof_document_bitset_index_new (G_LIST_MODEL (self), marks); + g_autoptr(SysprofMarkCatalog) names_catalog = _sysprof_mark_catalog_new (group_name, name, model); + + g_list_store_append (group, names_catalog); + } + + g_list_store_append (store, group); + } + + return G_LIST_MODEL (store); +} + +const SysprofTimeSpan * +sysprof_document_get_time_span (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return &self->time_span; +} + +/** + * sysprof_document_find_counter: + * @self: a #SysprofDocument + * @category: the category of the counter + * @name: the name of the counter + * + * Finds the first counter matching @category and @name. + * + * Returns: (transfer full) (nullable): a #SysprofDocumentCounter or %NULL + */ +SysprofDocumentCounter * +sysprof_document_find_counter (SysprofDocument *self, + const char *category, + const char *name) +{ + g_autoptr(GListModel) counters = NULL; + guint n_items; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (category != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + + counters = sysprof_document_list_counters (self); + n_items = g_list_model_get_n_items (counters); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentCounter) counter = g_list_model_get_item (counters, i); + + if (g_strcmp0 (category, sysprof_document_counter_get_category (counter)) == 0 && + g_strcmp0 (name, sysprof_document_counter_get_name (counter)) == 0) + return g_steal_pointer (&counter); + } + + return NULL; +} + +char * +sysprof_document_dup_title (SysprofDocument *self) +{ + g_autoptr(GDateTime) date_time = NULL; + + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + if (self->title != NULL) + return g_strdup (self->title); + + if ((date_time = g_date_time_new_from_iso8601 (self->header.capture_time, NULL))) + return g_date_time_format (date_time, _("Recording at %X %x")); + + return g_strdup_printf (_("Recording at %s"), self->header.capture_time); +} + +void +_sysprof_document_set_title (SysprofDocument *self, + const char *title) +{ + g_return_if_fail (SYSPROF_IS_DOCUMENT (self)); + + if (g_set_str (&self->title, title)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +/** + * sysprof_document_list_cpu_info: + * @self: a #SysprofDocument + * + * Gets the CPU that were discovered from the capture. + * + * Returns: (transfer full): a #GListModel of #SysprofCpuInfo + */ +GListModel * +sysprof_document_list_cpu_info (SysprofDocument *self) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + + return g_object_ref (G_LIST_MODEL (self->cpu_info)); +} + +static int +sort_symbols_for_bsearch (gconstpointer a, + gconstpointer b) +{ + const SysprofPackedSymbol *packed_a = a; + const SysprofPackedSymbol *packed_b = b; + + if (packed_a->pid < packed_b->pid) + return -1; + + if (packed_a->pid > packed_b->pid) + return 1; + + if (packed_a->addr_begin < packed_b->addr_begin) + return -1; + + if (packed_a->addr_begin > packed_b->addr_begin) + return 1; + + return 0; +} + +static DexFuture * +sysprof_document_serialize_symbols_fiber (gpointer user_data) +{ + static const guint8 empty_string[1] = {0}; + static const SysprofPackedSymbol empty_symbol = {0}; + SysprofDocument *self = user_data; + g_autoptr(GByteArray) strings = NULL; + g_autoptr(GHashTable) strings_offset = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GArray) packed_symbols = NULL; + GHashTableIter iter; + gpointer value; + char *data; + gsize packed_len; + + g_assert (SYSPROF_IS_DOCUMENT (self)); + + packed_symbols = g_array_new (FALSE, FALSE, sizeof (SysprofPackedSymbol)); + strings = g_byte_array_new (); + strings_offset = g_hash_table_new (g_str_hash, g_str_equal); + + /* Always put empty string at head so 0 is never a valid + * offset for the document entries except for "". + */ + g_byte_array_append (strings, empty_string, sizeof empty_string); + g_hash_table_insert (strings_offset, "", 0); + + g_hash_table_iter_init (&iter, self->pid_to_process_info); + while (g_hash_table_iter_next (&iter, NULL, &value)) + { + const SysprofProcessInfo *process_info = value; + + if (process_info->symbol_cache != NULL) + sysprof_symbol_cache_populate_packed (process_info->symbol_cache, + packed_symbols, + strings, + strings_offset, + process_info->pid); + } + + g_array_sort (packed_symbols, sort_symbols_for_bsearch); + g_array_append_val (packed_symbols, empty_symbol); + + packed_len = sizeof (SysprofPackedSymbol) * packed_symbols->len; + + /* Update the offsets to be relative to the beginning of the + * section containing our packed symbols. Ignore the last symbol + * so that it stays all zero. + */ + for (guint i = 0; i < packed_symbols->len-1; i++) + { + g_array_index (packed_symbols, SysprofPackedSymbol, i).offset += packed_len; + g_array_index (packed_symbols, SysprofPackedSymbol, i).tag_offset += packed_len; + } + + if (G_MAXSSIZE - packed_len < strings->len) + return dex_future_new_for_errno (ENOMEM); + + data = g_malloc (packed_len + strings->len); + memcpy (data, packed_symbols->data, packed_len); + memcpy (data+packed_len, strings->data, strings->len); + + bytes = g_bytes_new_take (data, packed_len + strings->len); + + return dex_future_new_take_boxed (G_TYPE_BYTES, g_steal_pointer (&bytes)); +} + +void +sysprof_document_serialize_symbols_async (SysprofDocument *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(DexAsyncResult) result = NULL; + + g_return_if_fail (SYSPROF_IS_DOCUMENT (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + result = dex_async_result_new (self, cancellable, callback, user_data); + + dex_async_result_await (result, + dex_scheduler_spawn (dex_thread_pool_scheduler_get_default (), 0, + sysprof_document_serialize_symbols_fiber, + g_object_ref (self), + g_object_unref)); +} + +/** + * sysprof_document_serialize_symbols_finish: + * @self: a #SysprofDocument + * @result: a #GAsyncResult + * @error: a location for a #GError + * + * Completes a request to serialize the symbols of the document + * encoded in a format that Sysprof understands. + * + * Returns: (transfer full): a #GBytes if successful; otherwise %NULL + * and @error is set. + */ +GBytes * +sysprof_document_serialize_symbols_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), NULL); + g_return_val_if_fail (DEX_IS_ASYNC_RESULT (result), NULL); + + return dex_async_result_propagate_pointer (DEX_ASYNC_RESULT (result), error); +} diff --git a/src/libsysprof/sysprof-document.h b/src/libsysprof/sysprof-document.h new file mode 100644 index 00000000..2eb85fa0 --- /dev/null +++ b/src/libsysprof/sysprof-document.h @@ -0,0 +1,118 @@ +/* sysprof-document.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-callgraph.h" +#include "sysprof-document-counter.h" +#include "sysprof-document-file.h" +#include "sysprof-document-traceable.h" +#include "sysprof-mark-catalog.h" +#include "sysprof-symbol.h" +#include "sysprof-time-span.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DOCUMENT (sysprof_document_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofDocument, sysprof_document, SYSPROF, DOCUMENT, GObject) + +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_document_dup_title (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +const SysprofTimeSpan *sysprof_document_get_time_span (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +SysprofDocumentFile *sysprof_document_lookup_file (SysprofDocument *self, + const char *path); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_cpu_info (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_files (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_traceables (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_allocations (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_logs (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_metadata (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_samples (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_samples_with_context_switch (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_samples_without_context_switch (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_processes (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_jitmaps (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_counters (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_marks (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_marks_by_group (SysprofDocument *self, + const char *group); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_catalog_marks (SysprofDocument *self); +SYSPROF_AVAILABLE_IN_ALL +SysprofDocumentCounter *sysprof_document_find_counter (SysprofDocument *self, + const char *category, + const char *name); +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_document_list_symbols_in_traceable (SysprofDocument *self, + SysprofDocumentTraceable *traceable); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_document_symbolize_traceable (SysprofDocument *self, + SysprofDocumentTraceable *traceable, + SysprofSymbol **symbols, + guint n_symbols, + SysprofAddressContext *final_context); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_callgraph_async (SysprofDocument *self, + SysprofCallgraphFlags flags, + GListModel *traceables, + gsize augment_size, + SysprofAugmentationFunc augment_func, + gpointer augment_func_data, + GDestroyNotify augment_func_data_destroy, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +SysprofCallgraph *sysprof_document_callgraph_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_document_serialize_symbols_async (SysprofDocument *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +GBytes *sysprof_document_serialize_symbols_finish (SysprofDocument *self, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-elf-loader-private.h b/src/libsysprof/sysprof-elf-loader-private.h new file mode 100644 index 00000000..363ca751 --- /dev/null +++ b/src/libsysprof/sysprof-elf-loader-private.h @@ -0,0 +1,50 @@ +/* + * sysprof-elf-loader-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-elf-private.h" +#include "sysprof-mount-namespace-private.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_ELF_LOADER (sysprof_elf_loader_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofElfLoader, sysprof_elf_loader, SYSPROF, ELF_LOADER, GObject) + +SysprofElfLoader *sysprof_elf_loader_new (void); +const char * const *sysprof_elf_loader_get_debug_dirs (SysprofElfLoader *self); +void sysprof_elf_loader_set_debug_dirs (SysprofElfLoader *self, + const char * const *debug_dirs); +const char * const *sysprof_elf_loader_get_external_debug_dirs (SysprofElfLoader *self); +void sysprof_elf_loader_set_external_debug_dirs (SysprofElfLoader *self, + const char * const *debug_dirs); +SysprofElf *sysprof_elf_loader_load (SysprofElfLoader *self, + SysprofMountNamespace *mount_namespace, + const char *file, + const char *build_id, + guint64 file_inode, + GError **error); + + +G_END_DECLS diff --git a/src/libsysprof/sysprof-elf-loader.c b/src/libsysprof/sysprof-elf-loader.c new file mode 100644 index 00000000..31f02545 --- /dev/null +++ b/src/libsysprof/sysprof-elf-loader.c @@ -0,0 +1,499 @@ +/* sysprof-elf-loader.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include "sysprof-elf-private.h" +#include "sysprof-elf-loader-private.h" +#include "sysprof-strings-private.h" + +#define DEFAULT_DEBUG_DIRS SYSPROF_STRV_INIT("/usr/lib/debug") + +struct _SysprofElfLoader +{ + GObject parent_instance; + GHashTable *cache; + char **debug_dirs; + char **external_debug_dirs; +}; + +enum { + PROP_0, + PROP_DEBUG_DIRS, + PROP_EXTERNAL_DEBUG_DIRS, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofElfLoader, sysprof_elf_loader, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; +static gboolean in_flatpak; +static gboolean in_podman; + +SysprofElfLoader * +sysprof_elf_loader_new (void) +{ + return g_object_new (SYSPROF_TYPE_ELF_LOADER, NULL); +} + +static void +_g_object_xunref (gpointer data) +{ + if (data != NULL) + g_object_unref (data); +} + +static void +sysprof_elf_loader_finalize (GObject *object) +{ + SysprofElfLoader *self = (SysprofElfLoader *)object; + + g_clear_pointer (&self->debug_dirs, g_strfreev); + g_clear_pointer (&self->external_debug_dirs, g_strfreev); + g_clear_pointer (&self->cache, g_hash_table_unref); + + G_OBJECT_CLASS (sysprof_elf_loader_parent_class)->finalize (object); +} + +static void +sysprof_elf_loader_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofElfLoader *self = SYSPROF_ELF_LOADER (object); + + switch (prop_id) + { + case PROP_DEBUG_DIRS: + g_value_set_boxed (value, sysprof_elf_loader_get_debug_dirs (self)); + break; + + case PROP_EXTERNAL_DEBUG_DIRS: + g_value_set_boxed (value, sysprof_elf_loader_get_external_debug_dirs (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_elf_loader_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofElfLoader *self = SYSPROF_ELF_LOADER (object); + + switch (prop_id) + { + case PROP_DEBUG_DIRS: + sysprof_elf_loader_set_debug_dirs (self, g_value_get_boxed (value)); + break; + + case PROP_EXTERNAL_DEBUG_DIRS: + sysprof_elf_loader_set_external_debug_dirs (self, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_elf_loader_class_init (SysprofElfLoaderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_elf_loader_finalize; + object_class->get_property = sysprof_elf_loader_get_property; + object_class->set_property = sysprof_elf_loader_set_property; + + properties [PROP_DEBUG_DIRS] = + g_param_spec_boxed ("debug-dirs", NULL, NULL, + G_TYPE_STRV, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_EXTERNAL_DEBUG_DIRS] = + g_param_spec_boxed ("external-debug-dirs", NULL, NULL, + G_TYPE_STRV, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + in_flatpak = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); + in_podman = g_file_test ("/run/.containerenv", G_FILE_TEST_EXISTS); +} + +static void +sysprof_elf_loader_init (SysprofElfLoader *self) +{ + self->debug_dirs = g_strdupv ((char **)DEFAULT_DEBUG_DIRS); + self->cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, _g_object_xunref); +} + +/** + * sysprof_elf_loader_get_debug_dirs: + * @self: a #SysprofElfLoader + * + * Gets the #SysprofElfLoader:debug-dirs property. + * + * See sysprof_elf_loader_set_debug_dirs() for information on how + * these directories are used. + * + * Returns: (nullable): an array of debug directories, or %NULL + */ +const char * const * +sysprof_elf_loader_get_debug_dirs (SysprofElfLoader *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF_LOADER (self), NULL); + + return (const char * const *)self->debug_dirs; +} + +/** + * sysprof_elf_loader_set_debug_dirs: + * @self: a #SysprofElfLoader + * @debug_dirs: the new debug directories to use + * + * Sets the #SysprofElfLoader:debug-dirs prpoerty. + * + * If @debug_dirs is %NULL, the default debug directories will + * be used. + * + * These directories will be used to resolve debug symbols within + * the mount namespace of the process whose symbols are being + * resolved. + * + * To set a global directory that may contain debug symbols, use + * sysprof_elf_loader_set_external_debug_dirs() which can be searched + * regardless of what mount namespace the resolving process was in. + */ +void +sysprof_elf_loader_set_debug_dirs (SysprofElfLoader *self, + const char * const *debug_dirs) +{ + g_return_if_fail (SYSPROF_IS_ELF_LOADER (self)); + g_return_if_fail (self->debug_dirs != NULL); + + if (sysprof_set_strv (&self->debug_dirs, debug_dirs)) + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUG_DIRS]); +} + +/** + * sysprof_elf_loader_get_external_debug_dirs: + * @self: a #SysprofElfLoader + * + * Gets the #SysprofElfLoader:external-debug-dirs property. + * + * See sysprof_elf_loader_set_external_debug_dirs() for how this + * property is used to locate ELF files. + * + * Returns: (nullable): an array of external debug directories, or %NULL + */ +const char * const * +sysprof_elf_loader_get_external_debug_dirs (SysprofElfLoader *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF_LOADER (self), NULL); + + return (const char * const *)self->external_debug_dirs; +} + +/** + * sysprof_elf_loader_set_external_debug_dirs: + * @self: a #SysprofElfLoader + * @external_debug_dirs: (nullable): array of debug directories to resolve + * `.gnu_debuglink` references in ELF files. + * + * Sets the #SysprofElfLoader:external-debug-dirs property. + * + * This is used to resolve `.gnu_debuglink` files across any process that is + * loading ELF files with this #SysprofElfLoader. That allows for symbolizing + * a capture file that was recording on a different system for which the + * debug symbols only exist locally. + * + * Note that this has no effect on stack trace unwinding, that must occur + * independently of this, when the samples are recorded by the specific + * unwinder in use. + */ +void +sysprof_elf_loader_set_external_debug_dirs (SysprofElfLoader *self, + const char * const *external_debug_dirs) +{ + g_return_if_fail (SYSPROF_IS_ELF_LOADER (self)); + + if (sysprof_set_strv (&self->external_debug_dirs, external_debug_dirs)) + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EXTERNAL_DEBUG_DIRS]); +} + +static char * +access_path_from_container (const char *path) +{ + if ((in_flatpak && !g_str_has_prefix (path, "/home/")) || in_podman) + return g_build_filename ("/var/run/host", path, NULL); + return g_strdup (path); +} + +static SysprofElf * +get_deepest_debuglink (SysprofElf *elf) +{ + SysprofElf *debug_link = sysprof_elf_get_debug_link_elf (elf); + return debug_link ? get_deepest_debuglink (debug_link) : elf; +} + +static gboolean +try_load_build_id (SysprofElfLoader *self, + SysprofMountNamespace *mount_namespace, + SysprofElf *elf, + const char *build_id, + const char *debug_dir) +{ + g_assert (SYSPROF_IS_ELF_LOADER (self)); + g_assert (!mount_namespace || SYSPROF_IS_MOUNT_NAMESPACE (mount_namespace)); + g_assert (SYSPROF_IS_ELF (elf)); + + if (build_id && build_id[0] && build_id[1]) + { + char prefix[3] = {build_id[0], build_id[1], 0}; + g_autofree char *build_id_path = g_build_filename (debug_dir, ".build-id", prefix, build_id, NULL); + g_autoptr(SysprofElf) debug_link_elf = sysprof_elf_loader_load (self, mount_namespace, build_id_path, build_id, 0, NULL); + + if (debug_link_elf != NULL) + { + sysprof_elf_set_debug_link_elf (elf, debug_link_elf); + return TRUE; + } + } + + return FALSE; +} + +static void +sysprof_elf_loader_annotate (SysprofElfLoader *self, + SysprofMountNamespace *mount_namespace, + const char *orig_file, + SysprofElf *elf, + const char *debug_link) +{ + g_assert (SYSPROF_IS_ELF_LOADER (self)); + g_assert (SYSPROF_IS_MOUNT_NAMESPACE (mount_namespace)); + g_assert (SYSPROF_IS_ELF (elf)); + g_assert (debug_link != NULL); + + if (self->debug_dirs != NULL) + { + for (guint i = 0; self->debug_dirs[i]; i++) + { + g_autoptr(SysprofElf) debug_link_elf = NULL; + g_autofree char *directory_name = NULL; + g_autofree char *debug_path = NULL; + g_autofree char *container_path = NULL; + const char *debug_dir = self->debug_dirs[i]; + const char *build_id; + + directory_name = g_path_get_dirname (orig_file); + debug_path = g_build_filename (debug_dir, directory_name, debug_link, NULL); + build_id = sysprof_elf_get_build_id (elf); + + if (try_load_build_id (self, mount_namespace, elf, build_id, debug_dir)) + return; + + if ((debug_link_elf = sysprof_elf_loader_load (self, mount_namespace, debug_path, build_id, 0, NULL))) + { + sysprof_elf_set_debug_link_elf (elf, get_deepest_debuglink (debug_link_elf)); + return; + } + } + } + + if (self->external_debug_dirs != NULL) + { + for (guint i = 0; self->external_debug_dirs[i]; i++) + { + g_autoptr(SysprofElf) debug_link_elf = NULL; + g_autofree char *directory_name = NULL; + g_autofree char *debug_path = NULL; + g_autofree char *container_path = NULL; + const char *debug_dir = self->external_debug_dirs[i]; + const char *build_id; + + directory_name = g_path_get_dirname (orig_file); + debug_path = g_build_filename (debug_dir, directory_name, debug_link, NULL); + build_id = sysprof_elf_get_build_id (elf); + + if (try_load_build_id (self, NULL, elf, build_id, debug_dir)) + return; + + if ((debug_link_elf = sysprof_elf_loader_load (self, NULL, debug_path, build_id, 0, NULL))) + { + sysprof_elf_set_debug_link_elf (elf, get_deepest_debuglink (debug_link_elf)); + return; + } + } + } +} + +static gboolean +get_file_and_inode (const char *path, + GMappedFile **mapped_file, + guint64 *file_inode) +{ + struct stat stbuf; + int fd; + + g_assert (path != NULL); + g_assert (mapped_file != NULL); + g_assert (file_inode != NULL); + + *mapped_file = NULL; + *file_inode = 0; + + fd = open (path, O_RDONLY|O_CLOEXEC, 0); + if (fd == -1) + return FALSE; + + if (fstat (fd, &stbuf) == 0) + *file_inode = (guint64)stbuf.st_ino; + *mapped_file = g_mapped_file_new_from_fd (fd, FALSE, NULL); + close (fd); + + return *mapped_file != NULL; +} + +/** + * sysprof_elf_loader_load: + * @self: a #SysprofElfLoader + * @mount_namespace: a #SysprofMountNamespace for path resolving + * @file: the path of the file to load within the mount namespace + * @build_id: (nullable): an optional build-id that can be used to resolve + * the file alternatively to the file path + * @file_inode: expected inode for @file + * @error: a location for a #GError, or %NULL + * + * Attempts to load a #SysprofElf for @file (or optionally by @build_id). + * + * This attempts to follow `.gnu_debuglink` ELF section headers and attach + * them to the resulting #SysprofElf so that additional symbol information + * is available. + * + * Returns: (transfer full): a #SysprofElf, or %NULL if the file could + * not be resolved. + */ +SysprofElf * +sysprof_elf_loader_load (SysprofElfLoader *self, + SysprofMountNamespace *mount_namespace, + const char *file, + const char *build_id, + guint64 file_inode, + GError **error) +{ + const char * const fallback_paths[2] = { file, NULL }; + g_auto(GStrv) paths = NULL; + + g_return_val_if_fail (SYSPROF_IS_ELF_LOADER (self), NULL); + g_return_val_if_fail (!mount_namespace || SYSPROF_IS_MOUNT_NAMESPACE (mount_namespace), NULL); + + /* We must translate the file into a number of paths that may possibly + * locate the file in the case that there are overlays in the mount + * namespace. Each of the paths could be in a lower overlay layer. + * + * To allow for zero-translation, we allow a NULL mount namespace. + * sysprof_elf_loader_annotate() will use that to load from external + * directories for which on additional translation is necessary. + */ + if (mount_namespace == NULL) + paths = g_strdupv ((char **)fallback_paths); + else + paths = sysprof_mount_namespace_translate (mount_namespace, file); + + if (paths == NULL) + goto failure; + + for (guint i = 0; paths[i]; i++) + { + g_autoptr(GMappedFile) mapped_file = NULL; + g_autoptr(SysprofElf) elf = NULL; + g_autoptr(GError) local_error = NULL; + g_autofree char *container_path = NULL; + SysprofElf *cached_elf = NULL; + const char *path = paths[i]; + const char *debug_link; + guint64 mapped_file_inode; + + if (in_flatpak || in_podman) + path = container_path = access_path_from_container (path); + + /* Lookup to see if we've already parsed this ELF and handle cases where + * we've failed to load it too. In the case we failed to load a key is + * stored in the cache with a NULL value. + */ + if (g_hash_table_lookup_extended (self->cache, path, NULL, (gpointer *)&cached_elf)) + { + if (cached_elf != NULL) + { + if (sysprof_elf_matches (cached_elf, file_inode, build_id)) + return g_object_ref (cached_elf); + } + + continue; + } + + /* Try to mmap the file and parse it. If the parser fails to parse the + * section headers, then this probably isn't an ELF file and we should + * store a failure record in the cache so that we don't attempt to load + * it again. + */ + if (get_file_and_inode (path, &mapped_file, &mapped_file_inode)) + elf = sysprof_elf_new (path, g_steal_pointer (&mapped_file), mapped_file_inode, &local_error); + + g_hash_table_insert (self->cache, + g_strdup (path), + elf ? g_object_ref (elf) : NULL); + + if (elf != NULL) + { + if (mount_namespace && (debug_link = sysprof_elf_get_debug_link (elf))) + sysprof_elf_loader_annotate (self, mount_namespace, file, elf, debug_link); + + /* If we loaded the ELF, but it doesn't match what this request is looking + * for in terms of inode/build-id, then we need to bail and not return it. + * We can, however, leave it in the cache incase another process/sample + * will need the ELF. + */ + if (!sysprof_elf_matches (elf, file_inode, build_id)) + g_clear_object (&elf); + } + + if (elf != NULL) + return g_steal_pointer (&elf); + } + +failure: + g_set_error_literal (error, + G_FILE_ERROR, + G_FILE_ERROR_NOENT, + "Failed to locate file"); + + return NULL; +} diff --git a/src/libsysprof/sysprof-elf-private.h b/src/libsysprof/sysprof-elf-private.h new file mode 100644 index 00000000..b4d0f737 --- /dev/null +++ b/src/libsysprof/sysprof-elf-private.h @@ -0,0 +1,51 @@ +/* sysprof-elf-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_ELF (sysprof_elf_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofElf, sysprof_elf, SYSPROF, ELF, GObject) + +SysprofElf *sysprof_elf_new (const char *filename, + GMappedFile *mapped_file, + guint64 file_inode, + GError **error); +gboolean sysprof_elf_matches (SysprofElf *self, + guint64 file_inode, + const char *build_id); +const char *sysprof_elf_get_nick (SysprofElf *self); +const char *sysprof_elf_get_file (SysprofElf *self); +const char *sysprof_elf_get_build_id (SysprofElf *self); +const char *sysprof_elf_get_debug_link (SysprofElf *self); +char *sysprof_elf_get_symbol_at_address (SysprofElf *self, + guint64 address, + guint64 *begin_address, + guint64 *end_address, + gboolean *is_fallback); +SysprofElf *sysprof_elf_get_debug_link_elf (SysprofElf *self); +void sysprof_elf_set_debug_link_elf (SysprofElf *self, + SysprofElf *debug_link_elf); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-elf-symbol-resolver-private.h b/src/libsysprof/sysprof-elf-symbol-resolver-private.h deleted file mode 100644 index 77b65ff9..00000000 --- a/src/libsysprof/sysprof-elf-symbol-resolver-private.h +++ /dev/null @@ -1,33 +0,0 @@ -/* sysprof-elf-symbol-resolver-private.h - * - * Copyright 2021 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-elf-symbol-resolver.h" - -G_BEGIN_DECLS - -char *_sysprof_elf_symbol_resolver_resolve_path (SysprofElfSymbolResolver *self, - GPid pid, - const char *path); -const char *_sysprof_elf_symbol_resolver_get_pid_kind (SysprofElfSymbolResolver *self, - GPid pid); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-elf-symbol-resolver.c b/src/libsysprof/sysprof-elf-symbol-resolver.c deleted file mode 100644 index bd9b82c5..00000000 --- a/src/libsysprof/sysprof-elf-symbol-resolver.c +++ /dev/null @@ -1,686 +0,0 @@ -/* sysprof-elf-symbol-resolver.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include - -#include "binfile.h" -#include "elfparser.h" -#include "sysprof-elf-symbol-resolver.h" -#include "sysprof-flatpak.h" -#include "sysprof-map-lookaside.h" -#include "sysprof-path-resolver.h" -#include "sysprof-podman.h" -#include "sysprof-symbol-resolver-private.h" - -typedef enum -{ - PROCESS_KIND_STANDARD, - PROCESS_KIND_FLATPAK, - PROCESS_KIND_PODMAN, -} ProcessKind; - -typedef struct -{ - char *on_host; - char *in_process; - int layer; -} ProcessOverlay; - -typedef struct -{ - SysprofMapLookaside *lookaside; - SysprofPathResolver *resolver; - GByteArray *mountinfo_data; - GArray *overlays; - char **debug_dirs; - char *info; - int pid; - guint kind : 2; -} ProcessInfo; - -struct _SysprofElfSymbolResolver -{ - GObject parent_instance; - - GHashTable *processes; - GStringChunk *chunks; - GHashTable *bin_files; - GHashTable *tag_cache; -}; - -static void symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofElfSymbolResolver, - sysprof_elf_symbol_resolver, - G_TYPE_OBJECT, - 0, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SYMBOL_RESOLVER, - symbol_resolver_iface_init)) - -static void -process_info_free (gpointer data) -{ - ProcessInfo *pi = data; - - if (pi != NULL) - { - g_clear_pointer (&pi->lookaside, sysprof_map_lookaside_free); - g_clear_pointer (&pi->resolver, _sysprof_path_resolver_free); - g_clear_pointer (&pi->mountinfo_data, g_byte_array_unref); - g_clear_pointer (&pi->overlays, g_array_unref); - g_clear_pointer (&pi->debug_dirs, g_strfreev); - g_clear_pointer (&pi->info, g_free); - g_slice_free (ProcessInfo, pi); - } -} - -static const char * const * -process_info_get_debug_dirs (const ProcessInfo *pi) -{ - static const char *standard[] = { "/usr/lib/debug", NULL }; - - if (pi->debug_dirs) - return (const char * const *) pi->debug_dirs; - - return standard; -} - -static void -sysprof_elf_symbol_resolver_finalize (GObject *object) -{ - SysprofElfSymbolResolver *self = (SysprofElfSymbolResolver *)object; - - g_clear_pointer (&self->bin_files, g_hash_table_unref); - g_clear_pointer (&self->tag_cache, g_hash_table_unref); - g_clear_pointer (&self->processes, g_hash_table_unref); - g_clear_pointer (&self->chunks, g_string_chunk_free); - - G_OBJECT_CLASS (sysprof_elf_symbol_resolver_parent_class)->finalize (object); -} - -static void -sysprof_elf_symbol_resolver_class_init (SysprofElfSymbolResolverClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_elf_symbol_resolver_finalize; -} - -static void -sysprof_elf_symbol_resolver_init (SysprofElfSymbolResolver *self) -{ - self->chunks = g_string_chunk_new (4096); - self->processes = g_hash_table_new_full (NULL, NULL, NULL, process_info_free); - self->bin_files = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - (GDestroyNotify)bin_file_free); - self->tag_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); -} - -static ProcessInfo * -sysprof_elf_symbol_resolver_get_process (SysprofElfSymbolResolver *self, - int pid) -{ - ProcessInfo *pi; - - g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); - - if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) - { - pi = g_slice_new0 (ProcessInfo); - pi->pid = pid; - g_hash_table_insert (self->processes, GINT_TO_POINTER (pid), pi); - } - - return pi; -} - -static void -sysprof_elf_symbol_resolver_load (SysprofSymbolResolver *resolver, - SysprofCaptureReader *reader) -{ - SysprofElfSymbolResolver *self = (SysprofElfSymbolResolver *)resolver; - static const guint8 zero[1] = {0}; - SysprofCaptureFrameType type; - g_autoptr(GByteArray) mounts = NULL; - g_autofree char *mounts_data = NULL; - GHashTableIter iter; - ProcessInfo *pi; - gpointer k, v; - - g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); - g_assert (reader != NULL); - - g_hash_table_remove_all (self->processes); - - /* First we need to load all the /proc/{pid}/mountinfo files so that - * we can discover what files within the processes filesystem namespace - * were mapped and where. We can use that information later to build - * path resolvers that let us locate the files from the host. - */ - sysprof_capture_reader_reset (reader); - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_FILE_CHUNK) - { - const SysprofCaptureFileChunk *ev; - int out_pid; - - if (!(ev = sysprof_capture_reader_read_file (reader))) - break; - - pi = sysprof_elf_symbol_resolver_get_process (self, ev->frame.pid); - - if (strcmp (ev->path, "/.flatpak-info") == 0) - { - pi->kind = PROCESS_KIND_FLATPAK; - g_free (pi->info); - pi->info = g_strndup ((char *)ev->data, ev->len); - } - else if (strcmp (ev->path, "/run/.containerenv") == 0) - { - pi->kind = PROCESS_KIND_PODMAN; - g_free (pi->info); - pi->info = g_strndup ((char *)ev->data, ev->len); - } - else if (g_str_has_prefix (ev->path, "/proc/") && - g_str_has_suffix (ev->path, "/mountinfo") && - sscanf (ev->path, "/proc/%u/mountinfo", &out_pid) == 1) - { - if (pi->mountinfo_data == NULL) - pi->mountinfo_data = g_byte_array_new (); - if (ev->len) - g_byte_array_append (pi->mountinfo_data, ev->data, ev->len); - } - else if (g_str_equal (ev->path, "/proc/mounts")) - { - if (mounts == NULL) - mounts = g_byte_array_new (); - if (ev->len) - g_byte_array_append (mounts, ev->data, ev->len); - } - } - else if (type == SYSPROF_CAPTURE_FRAME_OVERLAY) - { - const SysprofCaptureOverlay *ev; - ProcessOverlay ov; - - if (!(ev = sysprof_capture_reader_read_overlay (reader))) - break; - - ov.on_host = g_string_chunk_insert_const (self->chunks, ev->data); - ov.in_process = g_string_chunk_insert_const (self->chunks, &ev->data[ev->src_len+1]); - ov.layer = ev->layer; - - pi = sysprof_elf_symbol_resolver_get_process (self, ev->frame.pid); - if (pi->overlays == NULL) - pi->overlays = g_array_new (FALSE, FALSE, sizeof (ProcessOverlay)); - g_array_append_val (pi->overlays, ov); - } - else - { - if (!sysprof_capture_reader_skip (reader)) - break; - } - } - - /* Now make sure we have access to /proc/mounts data. If we do not find it - * within the capture, assume we're running on the same host. - */ - if (mounts != NULL) - { - g_byte_array_append (mounts, zero, 1); - mounts_data = (char *)g_byte_array_free (g_steal_pointer (&mounts), FALSE); - } - - if (mounts_data == NULL) - g_file_get_contents ("/proc/mounts", &mounts_data, NULL, NULL); - - /* Now that we loaded all the mountinfo data, we can create path resolvers - * for each of the processes. Once we have that data we can walk the file - * again to load the map events. - */ - g_hash_table_iter_init (&iter, self->processes); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - pi = v; - - if (pi->mountinfo_data == NULL) - continue; - - g_byte_array_append (pi->mountinfo_data, zero, 1); - - pi->resolver = _sysprof_path_resolver_new (mounts_data, - (const char *)pi->mountinfo_data->data); - - if (pi->overlays != NULL) - { - for (guint i = 0; i < pi->overlays->len; i++) - { - const ProcessOverlay *ov = &g_array_index (pi->overlays, ProcessOverlay, i); - _sysprof_path_resolver_add_overlay (pi->resolver, ov->in_process, ov->on_host, ov->layer); - } - } - - if (pi->kind == PROCESS_KIND_FLATPAK) - { - if (pi->info != NULL) - { - g_autoptr(GKeyFile) keyfile = g_key_file_new (); - - if (g_key_file_load_from_data (keyfile, pi->info, (gsize)-1, 0, NULL)) - { - if (g_key_file_has_group (keyfile, "Instance")) - { - g_autofree gchar *app_path = g_key_file_get_string (keyfile, "Instance", "app-path", NULL); - g_autofree gchar *runtime_path = g_key_file_get_string (keyfile, "Instance", "runtime-path", NULL); - g_autofree gchar *branch = g_key_file_get_string (keyfile, "Instance", "branch", NULL); - g_autofree gchar *arch = g_key_file_get_string (keyfile, "Instance", "arch", NULL); - g_autofree gchar *app_name = g_key_file_get_string (keyfile, "Application", "name", NULL); - g_autofree gchar *manifest_dir = g_path_get_dirname (app_path); - g_autofree gchar *manifest_path = g_build_filename (manifest_dir, "metadata", NULL); - g_autoptr(GKeyFile) manifest = g_key_file_new (); - GPtrArray *dirs = g_ptr_array_new (); - - /* TODO: extensions */ - g_ptr_array_add (dirs, g_build_filename (app_path, "lib", "debug", NULL)); - g_ptr_array_add (dirs, g_build_filename (runtime_path, "lib", "debug", NULL)); - - /* Try to figure out flatpak runtime debug symbol paths. */ - if (g_key_file_load_from_file (manifest, manifest_path, 0, NULL)) - { - /* Add the SDK debug extension. */ - g_autofree gchar *sdk = g_key_file_get_string (manifest, "Application", "sdk", NULL); - if (sdk) - { - /* Go from a string like "org.gnome.Sdk/x86_64/41" to "org.gnome.Sdk.Debug/x86_64/41". */ - g_autoptr(GString) debug = g_string_new (sdk); - g_string_replace (debug, "/", ".Debug/", 1); - - /* Construct a path like "/var/lib/flatpak/runtime/org.gnome.Sdk.Debug/x86_64/41/active/files". */ - g_ptr_array_add (dirs, g_build_filename ("/var/lib/flatpak/runtime", debug->str, "active/files", NULL)); - } - - /* Add the app's debug extension. */ - if (app_name && branch && arch) - { - /* Go from a string like "org.gnome.TextEditor" to "org.gnome.TextEditor.Debug". */ - g_autoptr(GString) debug = g_string_new (app_name); - g_string_append (debug, ".Debug"); - - /* Construct a path like "/var/lib/flatpak/runtime/org.gnome.TextEditor.Debug/x86_64/master/active/files". */ - g_ptr_array_add (dirs, g_build_filename ("/var/lib/flatpak/runtime", debug->str, arch, branch, "active/files", NULL)); - } - } - - g_ptr_array_add (dirs, 0); - pi->debug_dirs = (gchar**) g_ptr_array_free (dirs, FALSE); - } - } - } - } - else if (pi->kind == PROCESS_KIND_PODMAN) - { - pi->debug_dirs = g_new0 (gchar *, 2); - pi->debug_dirs[0] = _sysprof_path_resolver_resolve (pi->resolver, "/usr/lib/debug"); - pi->debug_dirs[1] = 0; - } - } - - /* Walk through the file again and extract maps so long as - * we have a resolver for them already. - */ - sysprof_capture_reader_reset (reader); - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_MAP) - { - const SysprofCaptureMap *ev = sysprof_capture_reader_read_map (reader); - const char *filename = ev->filename; - g_autofree char *resolved = NULL; - SysprofMap map; - - pi = sysprof_elf_symbol_resolver_get_process (self, ev->frame.pid); - - if (pi->resolver != NULL) - { - resolved = _sysprof_path_resolver_resolve (pi->resolver, filename); - - if (resolved) - filename = resolved; - } - - map.start = ev->start; - map.end = ev->end; - map.offset = ev->offset; - map.inode = ev->inode; - map.filename = filename; - - if (pi->lookaside == NULL) - pi->lookaside = sysprof_map_lookaside_new (); - - sysprof_map_lookaside_insert (pi->lookaside, &map); - } - else - { - if (!sysprof_capture_reader_skip (reader)) - return; - } - } -} - -static bin_file_t * -sysprof_elf_symbol_resolver_get_bin_file (SysprofElfSymbolResolver *self, - const ProcessInfo *pi, - const gchar *filename) -{ - g_autofree char *alternate = NULL; - const char * const *debug_dirs; - g_autofree char *on_host = NULL; - bin_file_t *bin_file; - - g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); - - if ((bin_file = g_hash_table_lookup (self->bin_files, filename))) - return bin_file; - - /* Debug dirs are going to be dependent on the process as different - * containers may affect where the debug symbols are installed. - */ - debug_dirs = process_info_get_debug_dirs (pi); - bin_file = bin_file_new (filename, (const char * const *)debug_dirs); - g_hash_table_insert (self->bin_files, g_strdup (filename), bin_file); - - return bin_file; -} - -static GQuark -guess_tag (SysprofElfSymbolResolver *self, - const SysprofMap *map) -{ - g_assert (map != NULL); - g_assert (map->filename != NULL); - - if (!g_hash_table_contains (self->tag_cache, map->filename)) - { - GQuark tag = 0; - - if (strstr (map->filename, "/libgobject-2.0.")) - tag = g_quark_from_static_string ("GObject"); - - else if (strstr (map->filename, "/libc.so.6")) - tag = g_quark_from_static_string ("libc"); - - else if (strstr (map->filename, "/libstdc++.so.6")) - tag = g_quark_from_static_string ("stdc++"); - - else if (strstr (map->filename, "/libglib-2.0.")) - tag = g_quark_from_static_string ("GLib"); - - else if (strstr (map->filename, "/libgio-2.0.")) - tag = g_quark_from_static_string ("Gio"); - - else if (strstr (map->filename, "/libgirepository-1.0.")) - tag = g_quark_from_static_string ("Introspection"); - - else if (strstr (map->filename, "/libgtk-4.")) - tag = g_quark_from_static_string ("Gtk 4"); - - else if (strstr (map->filename, "/libgtk-3.")) - tag = g_quark_from_static_string ("Gtk 3"); - - else if (strstr (map->filename, "/libgdk-3.")) - tag = g_quark_from_static_string ("Gdk 3"); - - else if (strstr (map->filename, "/libgtksourceview-3.0")) - tag = g_quark_from_static_string ("GtkSourceView-3"); - - else if (strstr (map->filename, "/libgtksourceview-4")) - tag = g_quark_from_static_string ("GtkSourceView-4"); - - else if (strstr (map->filename, "/libpixman-1")) - tag = g_quark_from_static_string ("Pixman"); - - else if (strstr (map->filename, "/libcairo.")) - tag = g_quark_from_static_string ("cairo"); - - else if (strstr (map->filename, "/libgstreamer-1.")) - tag = g_quark_from_static_string ("GStreamer"); - - else if (strstr (map->filename, "/libX11.")) - tag = g_quark_from_static_string ("X11"); - - else if (strstr (map->filename, "/libpango-1.0.")) - tag = g_quark_from_static_string ("Pango"); - - else if (strstr (map->filename, "/libpangocairo-1.0.")) - tag = g_quark_from_static_string ("Pango"); - - else if (strstr (map->filename, "/libpangomm-1.4.")) - tag = g_quark_from_static_string ("Pango"); - - else if (strstr (map->filename, "/libpangoft2-1.0")) - tag = g_quark_from_static_string ("Pango"); - - else if (strstr (map->filename, "/libpangoxft-1.0.")) - tag = g_quark_from_static_string ("Pango"); - - else if (strstr (map->filename, "/libclutter-")) - tag = g_quark_from_static_string ("Clutter"); - - else if (strstr (map->filename, "/libcogl.") || - strstr (map->filename, "/libcogl-")) - tag = g_quark_from_static_string ("Cogl"); - - else if (strstr (map->filename, "/libffi.")) - tag = g_quark_from_static_string ("libffi"); - - else if (strstr (map->filename, "/libwayland-")) - tag = g_quark_from_static_string ("Wayland"); - - else if (strstr (map->filename, "/libinput.")) - tag = g_quark_from_static_string ("libinput"); - - else if (strstr (map->filename, "/libgjs.")) - tag = g_quark_from_static_string ("Gjs"); - - else if (strstr (map->filename, "/libmozjs-")) - tag = g_quark_from_static_string ("MozJS"); - - else if (strstr (map->filename, "/libGL.")) - tag = g_quark_from_static_string ("GL"); - - else if (strstr (map->filename, "/libEGL.")) - tag = g_quark_from_static_string ("EGL"); - - g_hash_table_insert (self->tag_cache, - g_strdup (map->filename), - GSIZE_TO_POINTER (tag)); - } - - return GPOINTER_TO_SIZE (g_hash_table_lookup (self->tag_cache, map->filename)); -} - -gboolean -sysprof_elf_symbol_resolver_resolve_full (SysprofElfSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - SysprofCaptureAddress *begin, - SysprofCaptureAddress *end, - gchar **name, - GQuark *tag) -{ - const bin_symbol_t *bin_sym; - const gchar *bin_sym_name; - const SysprofMap *map; - ProcessInfo *pi; - bin_file_t *bin_file; - gulong ubegin; - gulong uend; - - g_assert (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self)); - g_assert (name != NULL); - g_assert (begin != NULL); - g_assert (end != NULL); - - *name = NULL; - - if (context != SYSPROF_ADDRESS_CONTEXT_USER) - return FALSE; - - if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) - return FALSE; - - if (pi->lookaside == NULL) - return FALSE; - - if (!(map = sysprof_map_lookaside_lookup (pi->lookaside, address))) - return FALSE; - - address -= map->start; - address += map->offset; - - bin_file = sysprof_elf_symbol_resolver_get_bin_file (self, pi, map->filename); - - g_assert (bin_file != NULL); - - /* PERF_RECORD_MMAP doesn't provide an inode, so we can't rely on that - * until we can get PERF_RECORD_MMAP2. - */ - if G_UNLIKELY (map->inode && !bin_file_check_inode (bin_file, map->inode)) - { - *name = g_strdup_printf ("%s: inode mismatch", map->filename); - return TRUE; - } - - bin_sym = bin_file_lookup_symbol (bin_file, address); - bin_sym_name = bin_symbol_get_name (bin_file, bin_sym); - - if G_LIKELY (map->filename) - *tag = guess_tag (self, map); - - *name = elf_demangle (bin_sym_name); - bin_symbol_get_address_range (bin_file, bin_sym, &ubegin, &uend); - - *begin = ubegin; - *end = uend; - - return TRUE; -} - -static gchar * -sysprof_elf_symbol_resolver_resolve_with_context (SysprofSymbolResolver *resolver, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag) -{ - gchar *name = NULL; - SysprofCaptureAddress begin, end; - - /* If not user context, nothing we can do here */ - if (context != SYSPROF_ADDRESS_CONTEXT_USER) - return NULL; - - /* If this is a jitmap entry, bail early to save some cycles */ - if ((address & SYSPROF_CAPTURE_JITMAP_MARK) == SYSPROF_CAPTURE_JITMAP_MARK) - return NULL; - - sysprof_elf_symbol_resolver_resolve_full (SYSPROF_ELF_SYMBOL_RESOLVER (resolver), - time, - pid, - context, - address, - &begin, - &end, - &name, - tag); - - return g_steal_pointer (&name); -} - -static void -symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface) -{ - iface->load = sysprof_elf_symbol_resolver_load; - iface->resolve_with_context = sysprof_elf_symbol_resolver_resolve_with_context; -} - -SysprofSymbolResolver * -sysprof_elf_symbol_resolver_new (void) -{ - return g_object_new (SYSPROF_TYPE_ELF_SYMBOL_RESOLVER, NULL); -} - -void -sysprof_elf_symbol_resolver_add_debug_dir (SysprofElfSymbolResolver *self, - const gchar *debug_dir) -{ - /* Do Nothing */ - /* XXX: Mark as deprecated post 41 or remove with Gtk4 port */ -} - -char * -_sysprof_elf_symbol_resolver_resolve_path (SysprofElfSymbolResolver *self, - GPid pid, - const char *path) -{ - ProcessInfo *pi; - - g_return_val_if_fail (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self), NULL); - - if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) - return NULL; - - if (pi->resolver == NULL) - return NULL; - - return _sysprof_path_resolver_resolve (pi->resolver, path); -} - -const char * -_sysprof_elf_symbol_resolver_get_pid_kind (SysprofElfSymbolResolver *self, - GPid pid) -{ - ProcessInfo *pi; - - g_return_val_if_fail (SYSPROF_IS_ELF_SYMBOL_RESOLVER (self), NULL); - - if (!(pi = g_hash_table_lookup (self->processes, GINT_TO_POINTER (pid)))) - return "unknown"; - - if (pi->kind == PROCESS_KIND_FLATPAK) - return "Flatpak"; - - if (pi->kind == PROCESS_KIND_PODMAN) - return "Podman"; - - if (pi->kind == PROCESS_KIND_STANDARD) - return "Standard"; - - return "unknown"; -} diff --git a/src/libsysprof/sysprof-elf-symbol-resolver.h b/src/libsysprof/sysprof-elf-symbol-resolver.h deleted file mode 100644 index 6b8e5fdc..00000000 --- a/src/libsysprof/sysprof-elf-symbol-resolver.h +++ /dev/null @@ -1,53 +0,0 @@ -/* sysprof-elf-symbol-resolver.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-symbol-resolver.h" -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_ELF_SYMBOL_RESOLVER (sysprof_elf_symbol_resolver_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofElfSymbolResolver, sysprof_elf_symbol_resolver, SYSPROF, ELF_SYMBOL_RESOLVER, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSymbolResolver *sysprof_elf_symbol_resolver_new (void); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_elf_symbol_resolver_add_debug_dir (SysprofElfSymbolResolver *self, - const gchar *debug_dir); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_elf_symbol_resolver_resolve_full (SysprofElfSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - SysprofCaptureAddress *begin, - SysprofCaptureAddress *end, - gchar **name, - GQuark *tag); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-elf-symbolizer.c b/src/libsysprof/sysprof-elf-symbolizer.c new file mode 100644 index 00000000..d522cf38 --- /dev/null +++ b/src/libsysprof/sysprof-elf-symbolizer.c @@ -0,0 +1,312 @@ +/* sysprof-elf-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-elf-private.h" +#include "sysprof-elf-loader-private.h" +#include "sysprof-elf-symbolizer.h" +#include "sysprof-document-private.h" +#include "sysprof-strings-private.h" +#include "sysprof-symbolizer-private.h" +#include "sysprof-symbol-private.h" + +struct _SysprofElfSymbolizer +{ + SysprofSymbolizer parent_instance; + SysprofElfLoader *loader; +}; + +struct _SysprofElfSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofElfSymbolizer, sysprof_elf_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +enum { + PROP_0, + PROP_DEBUG_DIRS, + PROP_EXTERNAL_DEBUG_DIRS, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static SysprofSymbol * +sysprof_elf_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofElfSymbolizer *self = (SysprofElfSymbolizer *)symbolizer; + g_autoptr(SysprofElf) elf = NULL; + SysprofDocumentMmap *map; + g_autofree char *name = NULL; + const char *path; + const char *build_id; + gboolean is_fallback = FALSE; + guint64 map_begin; + guint64 map_end; + guint64 relative_address; + guint64 begin_address; + guint64 end_address; + guint64 file_inode; + guint64 file_offset; + SysprofSymbol *ret; + + if (process_info == NULL || + process_info->address_layout == NULL || + process_info->mount_namespace == NULL || + (context != SYSPROF_ADDRESS_CONTEXT_NONE && + context != SYSPROF_ADDRESS_CONTEXT_USER)) + return NULL; + + /* Always ignore jitmap functions, no matter the ordering */ + if ((address & 0xFFFFFFFF00000000) == 0xE000000000000000) + return NULL; + + /* First find out what was mapped at that address */ + if (!(map = sysprof_address_layout_lookup (process_info->address_layout, address))) + return NULL; + + map_begin = sysprof_document_mmap_get_start_address (map); + map_end = sysprof_document_mmap_get_end_address (map); + + g_assert (address >= map_begin); + g_assert (address < map_end); + + file_offset = sysprof_document_mmap_get_file_offset (map); + + relative_address = address; + relative_address -= map_begin; + relative_address += file_offset; + + path = sysprof_document_mmap_get_file (map); + build_id = sysprof_document_mmap_get_build_id (map); + file_inode = sysprof_document_mmap_get_file_inode (map); + + /* See if we can load an ELF at the path . It will be translated from the + * mount namespace into something hopefully we can access. + */ + if (!(elf = sysprof_elf_loader_load (self->loader, + process_info->mount_namespace, + path, + build_id, + file_inode, + NULL))) + goto fallback; + + /* Try to get the symbol name at the address and the begin/end address + * so that it can be inserted into our symbol cache. + */ + if (!(name = sysprof_elf_get_symbol_at_address (elf, + relative_address, + &begin_address, + &end_address, + &is_fallback))) + goto fallback; + + /* Sanitize address ranges if we have to. Sometimes that can happen + * for us, but it seems to be limited to glibc. + */ + begin_address = CLAMP (begin_address, file_offset, file_offset + (map_end - map_begin)); + end_address = CLAMP (end_address, file_offset, file_offset + (map_end - map_begin)); + if (end_address == begin_address) + end_address++; + + ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), + sysprof_strings_get (strings, path), + sysprof_strings_get (strings, sysprof_elf_get_nick (elf)), + map_begin + (begin_address - file_offset), + map_begin + (end_address - file_offset), + SYSPROF_SYMBOL_KIND_USER); + ret->is_fallback = is_fallback; + + return ret; + +fallback: + /* Fallback, we failed to locate the symbol within a file we can + * access, so tell the user about what file contained the symbol + * and where (relative to that file) the IP was. + */ + name = g_strdup_printf ("In File %s+0x%"G_GINT64_MODIFIER"x", + sysprof_document_mmap_get_file (map), + relative_address); + begin_address = address; + end_address = address + 1; + + ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), + NULL, NULL, begin_address, end_address, + SYSPROF_SYMBOL_KIND_USER); + ret->is_fallback = TRUE; + + return ret; +} + +static void +sysprof_elf_symbolizer_loader_notify_cb (SysprofElfSymbolizer *self, + GParamSpec *pspec, + SysprofElfLoader *loader) +{ + g_assert (SYSPROF_IS_ELF_SYMBOLIZER (self)); + g_assert (pspec != NULL); + g_assert (SYSPROF_IS_ELF_LOADER (loader)); + + if (0) {} + else if (g_strcmp0 (pspec->name, "debug-dirs") == 0) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DEBUG_DIRS]); + else if (g_strcmp0 (pspec->name, "external-debug-dirs") == 0) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXTERNAL_DEBUG_DIRS]); +} + +static void +sysprof_elf_symbolizer_finalize (GObject *object) +{ + SysprofElfSymbolizer *self = (SysprofElfSymbolizer *)object; + + g_clear_object (&self->loader); + + G_OBJECT_CLASS (sysprof_elf_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_elf_symbolizer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofElfSymbolizer *self = SYSPROF_ELF_SYMBOLIZER (object); + + switch (prop_id) + { + case PROP_DEBUG_DIRS: + g_value_set_boxed (value, sysprof_elf_symbolizer_get_debug_dirs (self)); + break; + + case PROP_EXTERNAL_DEBUG_DIRS: + g_value_set_boxed (value, sysprof_elf_symbolizer_get_external_debug_dirs (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_elf_symbolizer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofElfSymbolizer *self = SYSPROF_ELF_SYMBOLIZER (object); + + switch (prop_id) + { + case PROP_DEBUG_DIRS: + sysprof_elf_symbolizer_set_debug_dirs (self, g_value_get_boxed (value)); + break; + + case PROP_EXTERNAL_DEBUG_DIRS: + sysprof_elf_symbolizer_set_external_debug_dirs (self, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_elf_symbolizer_class_init (SysprofElfSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->finalize = sysprof_elf_symbolizer_finalize; + object_class->get_property = sysprof_elf_symbolizer_get_property; + object_class->set_property = sysprof_elf_symbolizer_set_property; + + symbolizer_class->symbolize = sysprof_elf_symbolizer_symbolize; + + properties[PROP_DEBUG_DIRS] = + g_param_spec_boxed ("debug-dirs", NULL, NULL, + G_TYPE_STRV, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_EXTERNAL_DEBUG_DIRS] = + g_param_spec_boxed ("external-debug-dirs", NULL, NULL, + G_TYPE_STRV, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_elf_symbolizer_init (SysprofElfSymbolizer *self) +{ + self->loader = sysprof_elf_loader_new (); + + g_signal_connect_object (self->loader, + "notify", + G_CALLBACK (sysprof_elf_symbolizer_loader_notify_cb), + self, + G_CONNECT_SWAPPED); +} + +SysprofSymbolizer * +sysprof_elf_symbolizer_new (void) +{ + return g_object_new (SYSPROF_TYPE_ELF_SYMBOLIZER, NULL); +} + +const char * const * +sysprof_elf_symbolizer_get_debug_dirs (SysprofElfSymbolizer *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF_SYMBOLIZER (self), NULL); + + return sysprof_elf_loader_get_debug_dirs (self->loader); +} + +void +sysprof_elf_symbolizer_set_debug_dirs (SysprofElfSymbolizer *self, + const char * const *debug_dirs) +{ + g_return_if_fail (SYSPROF_IS_ELF_SYMBOLIZER (self)); + + sysprof_elf_loader_set_debug_dirs (self->loader, debug_dirs); +} + +const char * const * +sysprof_elf_symbolizer_get_external_debug_dirs (SysprofElfSymbolizer *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF_SYMBOLIZER (self), NULL); + + return sysprof_elf_loader_get_external_debug_dirs (self->loader); +} + +void +sysprof_elf_symbolizer_set_external_debug_dirs (SysprofElfSymbolizer *self, + const char * const *external_debug_dirs) +{ + g_return_if_fail (SYSPROF_IS_ELF_SYMBOLIZER (self)); + + sysprof_elf_loader_set_external_debug_dirs (self->loader, external_debug_dirs); +} diff --git a/src/libsysprof/sysprof-elf-symbolizer.h b/src/libsysprof/sysprof-elf-symbolizer.h new file mode 100644 index 00000000..7ea96e55 --- /dev/null +++ b/src/libsysprof/sysprof-elf-symbolizer.h @@ -0,0 +1,52 @@ +/* sysprof-elf-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_ELF_SYMBOLIZER (sysprof_elf_symbolizer_get_type()) +#define SYSPROF_IS_ELF_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_ELF_SYMBOLIZER) +#define SYSPROF_ELF_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_ELF_SYMBOLIZER, SysprofElfSymbolizer) +#define SYSPROF_ELF_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_ELF_SYMBOLIZER, SysprofElfSymbolizerClass) + +typedef struct _SysprofElfSymbolizer SysprofElfSymbolizer; +typedef struct _SysprofElfSymbolizerClass SysprofElfSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_elf_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_elf_symbolizer_new (void); +SYSPROF_AVAILABLE_IN_ALL +const char * const *sysprof_elf_symbolizer_get_debug_dirs (SysprofElfSymbolizer *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_elf_symbolizer_set_debug_dirs (SysprofElfSymbolizer *self, + const char * const *debug_dirs); +SYSPROF_AVAILABLE_IN_ALL +const char * const *sysprof_elf_symbolizer_get_external_debug_dirs (SysprofElfSymbolizer *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_elf_symbolizer_set_external_debug_dirs (SysprofElfSymbolizer *self, + const char * const *external_debug_dirs); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofElfSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-elf.c b/src/libsysprof/sysprof-elf.c new file mode 100644 index 00000000..693ab32f --- /dev/null +++ b/src/libsysprof/sysprof-elf.c @@ -0,0 +1,521 @@ +/* sysprof-elf.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "elfparser.h" + +#include "sysprof-elf-private.h" + +struct _SysprofElf +{ + GObject parent_instance; + const char *nick; + char *build_id; + char *file; + SysprofElf *debug_link_elf; + ElfParser *parser; + guint64 file_inode; + gulong text_offset; +}; + +enum { + PROP_0, + PROP_BUILD_ID, + PROP_DEBUG_LINK, + PROP_DEBUG_LINK_ELF, + PROP_FILE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofElf, sysprof_elf, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; +static GHashTable *nicks; +static const struct { + const char *library; + const char *nick; +} nick_table[] = { + { "libc.so", "libc" }, + { "libffi.so", "libffi" }, + { "ld-linux-x86-64.so", "glibc" }, + { "libnss_sss.so", "NSS" }, + { "libsss_debug.so", "SSSD" }, + { "libsss_util.so", "SSSD" }, + { "libnss_systemd.so", "NSS" }, + { "libpcre2-8.so", "PCRE" }, + { "libselinux.so", "SELinux" }, + { "libssl3.so", "NSS" }, + { "libstdc++.so", "libc" }, + { "libsystemd.so", "systemd" }, + { "libudev.so", "udev" }, + { "libxul.so", "XUL" }, + { "libz.so", "Zlib" }, + { "libzstd.so", "Zstd" }, + + /* GObject */ + { "libglib-2.0.so", "GLib" }, + { "libgobject-2.0.so", "GObject" }, + { "libgio-2.0.so", "Gio" }, + { "libgirepository-1.0.so", "Introspection" }, + + /* Cairo/Pixman */ + { "libcairo-gobject.so", "Cairo" }, + { "libcairo.so", "Cairo" }, + { "libpixman-1.so", "Pixman" }, + + /* Javascript */ + { "libgjs.so", "JS" }, + { "libmozjs-102.so", "JS" }, + { "libmozjs-115.so", "JS" }, + { "libjavascriptcoregtk-4.0.so", "JS" }, + { "libjavascriptcoregtk-4.1.so", "JS" }, + { "libjavascriptcoregtk-6.0.so", "JS" }, + + /* WebKit */ + { "libwebkit2gtk-4.0.so", "WebKit" }, + { "libwebkit2gtk-4.1.so", "WebKit" }, + { "libwebkitgtk-6.0.so", "WebKit" }, + + /* Various GNOME Platform Libraries */ + { "libdex-1.so", "Dex" }, + { "libgstreamer-1-0.so", "GStreamer" }, + { "libgudev-1.0.so", "udev" }, + { "libibus-1.0.so", "IBus" }, + { "libjson-glib-1.0.so", "JSON-GLib" }, + { "libjsonrpc-glib-1.0.so", "JSONRPC-GLib" }, + { "libpolkit-agent-1.so", "PolicyKit" }, + { "libpolkit-gobject-1.so", "PolicyKit" }, + { "libvte-2.91-gtk4.so", "VTE" }, + { "libvte-2.91.so", "VTE" }, + + /* Pango and Harfbuzz */ + { "libfribidi.so", "Fribidi" }, + { "libpango-1.0.so", "Pango" }, + { "libpangocairo-1.0.so", "Pango" }, + { "libpangoft2-1.0.so", "Pango" }, + { "libharfbuzz-cairo.so", "Harfbuzz" }, + { "libharfbuzz-gobject.so", "Harfbuzz" }, + { "libharfbuzz-icu.so", "Harfbuzz" }, + { "libharfbuzz-subset.so", "Harfbuzz" }, + { "libharfbuzz.so", "Harfbuzz" }, + { "libfontconfig.so", "FontConfig" }, + + /* GTK */ + { "libgtk-3.so", "GTK 3" }, + { "libgdk-3.so", "GTK 3" }, + { "libgtk-4.so", "GTK 4" }, + { "libadwaita-1.so", "Adwaita" }, + { "libgraphene-1.0.so", "Graphene" }, + { "libgdk_pixbuf-2.0.so", "GdkPixbuf" }, + { "librsvg-2.so", "rsvg" }, + + /* Xorg/X11 */ + { "libX11-xcb.so", "X11" }, + { "libX11.so", "X11" }, + { "libxcb.so", "X11" }, + { "libxkbcommon.so", "XKB" }, + + /* Wayland */ + { "libwayland-client.so", "Wayland Client" }, + { "libwayland-cursor.so", "Wayland Cursor" }, + { "libwayland-egl.so", "Wayland EGL" }, + { "libwayland-server.so", "Wayland Server" }, + + /* Mutter/Clutter/Shell */ + { "libclutter-1.0.so", "Clutter" }, + { "libclutter-glx-1.0.so", "Clutter" }, + { "libinput.so", "libinput" }, + { "libmutter-12.so", "Mutter" }, + { "libmutter-cogl-12.so", "Mutter" }, + { "libmutter-clutter-12.so", "Mutter" }, + { "libst-12.so", "GNOME Shell" }, + + /* GtkSourceView */ + { "libgtksourceview-3.0.so", "GtkSourceView" }, + { "libgtksourceview-4.so", "GtkSourceView" }, + { "libgtksourceview-5.so", "GtkSourceView" }, + + /* Pipewire and Legacy Audio modules */ + { "libasound.so", "ALSA" }, + { "libpipewire-0.3.so", "Pipewire" }, + { "libpipewire-module-client-node.so", "Pipewire" }, + { "libpipewire-module-protocol-pulse.so", "Pipewire" }, + { "libpulse.so", "PulseAudio" }, + { "libpulsecommon-16.1.so", "PulseAudio" }, + { "libspa-alsa.so", "Pipewire" }, + { "libspa-audioconvert.so", "Pipewire" }, + { "libspa-support.so", "Pipewire" }, + + /* OpenGL base libraries */ + { "libEGL.so", "EGL" }, + { "libGL.so", "GL" }, + + /* Mesa and DRI Drivers */ + { "crocus_dri.so", "Mesa" }, + { "i915_dri.so", "Mesa" }, + { "i965_drv_video.so", "Mesa" }, + { "iHD_drv_video.so", "Mesa" }, + { "iris_dri.so", "Mesa" }, + { "kms_swrast_dri.so", "Mesa" }, + { "libEGL_mesa.so", "Mesa" }, + { "libGLX_mesa.so", "Mesa" }, + { "libdrm.so", "Mesa" }, + { "nouveau_dri.so", "Mesa" }, + { "r300_dri.so", "Mesa" }, + { "r600_dri.so", "Mesa" }, + { "radeonsi_dri.so", "Mesa" }, + { "swrast_dri.so", "Mesa" }, + { "virtio_gpu_dri.so", "Mesa" }, + { "vmwgfx_dri.so", "Mesa" }, + { "zink_dri.so", "Mesa" }, + + /* Various cryptography libraries */ + { "libcrypto.so", "Cryptography" }, + { "libssl.so", "Cryptography" }, + { "libssl3.so", "Cryptography" }, + { "libgnutls.so", "Cryptography" }, + { "libgnutlsxx.so", "Cryptography" }, + { "libgnutls-dane.so", "Cryptography" }, +}; + +static void +sysprof_elf_finalize (GObject *object) +{ + SysprofElf *self = (SysprofElf *)object; + + g_clear_pointer (&self->file, g_free); + g_clear_pointer (&self->parser, elf_parser_free); + g_clear_object (&self->debug_link_elf); + + G_OBJECT_CLASS (sysprof_elf_parent_class)->finalize (object); +} + +static void +sysprof_elf_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofElf *self = SYSPROF_ELF (object); + + switch (prop_id) + { + case PROP_BUILD_ID: + g_value_set_string (value, sysprof_elf_get_build_id (self)); + break; + + case PROP_DEBUG_LINK: + g_value_set_string (value, sysprof_elf_get_debug_link (self)); + break; + + case PROP_DEBUG_LINK_ELF: + g_value_set_object (value, sysprof_elf_get_debug_link_elf (self)); + break; + + case PROP_FILE: + g_value_set_string (value, sysprof_elf_get_file (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_elf_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofElf *self = SYSPROF_ELF (object); + + switch (prop_id) + { + case PROP_DEBUG_LINK_ELF: + sysprof_elf_set_debug_link_elf (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_elf_class_init (SysprofElfClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_elf_finalize; + object_class->get_property = sysprof_elf_get_property; + object_class->set_property = sysprof_elf_set_property; + + properties [PROP_BUILD_ID] = + g_param_spec_string ("build-id", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DEBUG_LINK] = + g_param_spec_string ("debug-link", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DEBUG_LINK_ELF] = + g_param_spec_object ("debug-link-elf", NULL, NULL, + SYSPROF_TYPE_ELF, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILE] = + g_param_spec_string ("file", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + nicks = g_hash_table_new (g_str_hash, g_str_equal); + for (guint i = 0; i < G_N_ELEMENTS (nick_table); i++) + { + g_assert (g_str_has_suffix (nick_table[i].library, ".so")); + g_hash_table_insert (nicks, + (char *)nick_table[i].library, + (char *)nick_table[i].nick); + } +} + +static void +sysprof_elf_init (SysprofElf *self) +{ +} + +static void +guess_nick (SysprofElf *self, + const char *name, + const char *endptr) +{ + char key[32]; + + if (endptr <= name) + return; + + if (endptr - name >= sizeof key) + return; + + memcpy (key, name, endptr-name); + key[endptr-name] = 0; + + self->nick = g_hash_table_lookup (nicks, key); +} + +SysprofElf * +sysprof_elf_new (const char *filename, + GMappedFile *mapped_file, + guint64 file_inode, + GError **error) +{ + SysprofElf *self; + ElfParser *parser; + + g_return_val_if_fail (mapped_file != NULL, NULL); + + if (!(parser = elf_parser_new_from_mmap (g_steal_pointer (&mapped_file), error))) + return NULL; + + self = g_object_new (SYSPROF_TYPE_ELF, NULL); + self->file = g_strdup (filename); + self->parser = g_steal_pointer (&parser); + self->file_inode = file_inode; + self->text_offset = elf_parser_get_text_offset (self->parser); + + if (filename != NULL) + { + const char *base; + const char *endptr; + + if ((base = strrchr (filename, '/'))) + { + endptr = strstr (++base, ".so"); + + if (endptr != NULL && (endptr[3] == 0 || endptr[3] == '.')) + guess_nick (self, base, &endptr[3]); + } + } + + return self; +} + +const char * +sysprof_elf_get_file (SysprofElf *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF (self), NULL); + + return self->file; +} + +const char * +sysprof_elf_get_build_id (SysprofElf *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF (self), NULL); + + return elf_parser_get_build_id (self->parser); +} + +const char * +sysprof_elf_get_debug_link (SysprofElf *self) +{ + guint crc32; + + g_return_val_if_fail (SYSPROF_IS_ELF (self), NULL); + + return elf_parser_get_debug_link (self->parser, &crc32); +} + +static char * +sysprof_elf_get_symbol_at_address_internal (SysprofElf *self, + const char *filename, + guint64 address, + guint64 *begin_address, + guint64 *end_address, + guint64 text_offset, + gboolean *is_fallback) +{ + const ElfSym *symbol; + char *ret = NULL; + gulong begin = 0; + gulong end = 0; + + g_return_val_if_fail (SYSPROF_IS_ELF (self), NULL); + + if (self->debug_link_elf != NULL) + { + ret = sysprof_elf_get_symbol_at_address_internal (self->debug_link_elf, filename, address, begin_address, end_address, text_offset, is_fallback); + + if (ret != NULL) + return ret; + } + + if ((symbol = elf_parser_lookup_symbol (self->parser, address - text_offset))) + { + const char *name; + + if (begin_address || end_address) + { + elf_parser_get_sym_address_range (self->parser, symbol, &begin, &end); + begin += text_offset; + end += text_offset; + } + + name = elf_parser_get_sym_name (self->parser, symbol); + + if (name != NULL && name[0] == '_' && name[1] == 'Z') + ret = elf_demangle (name); + else + ret = g_strdup (name); + } + else + { + begin = address; + end = address + 1; + ret = g_strdup_printf ("In File %s+0x%"G_GINT64_MODIFIER"x", filename, address); + if (is_fallback) + *is_fallback = TRUE; + } + + if (begin_address) + *begin_address = begin; + + if (end_address) + *end_address = end; + + return ret; +} + +char * +sysprof_elf_get_symbol_at_address (SysprofElf *self, + guint64 address, + guint64 *begin_address, + guint64 *end_address, + gboolean *is_fallback) +{ + return sysprof_elf_get_symbol_at_address_internal (self, + self->file, + address, + begin_address, + end_address, + self->text_offset, + is_fallback); +} + +/** + * sysprof_elf_get_debug_link_elf: + * @self: a #SysprofElf + * + * Gets a #SysprofElf that was resolved from the `.gnu_debuglink` + * ELF section header. + * + * Returns: (transfer none) (nullable): a #SysprofElf or %NULL + */ +SysprofElf * +sysprof_elf_get_debug_link_elf (SysprofElf *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF (self), NULL); + + return self->debug_link_elf; +} + +void +sysprof_elf_set_debug_link_elf (SysprofElf *self, + SysprofElf *debug_link_elf) +{ + g_return_if_fail (SYSPROF_IS_ELF (self)); + g_return_if_fail (!debug_link_elf || SYSPROF_IS_ELF (debug_link_elf)); + + if (g_set_object (&self->debug_link_elf, debug_link_elf)) + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUG_LINK_ELF]); +} + +gboolean +sysprof_elf_matches (SysprofElf *self, + guint64 file_inode, + const char *build_id) +{ + g_return_val_if_fail (SYSPROF_IS_ELF (self), FALSE); + + if (build_id != NULL) + { + const char *elf_build_id = elf_parser_get_build_id (self->parser); + + /* Not matching build-id, you definitely don't want this ELF */ + if (elf_build_id != NULL && !g_str_equal (build_id, elf_build_id)) + return FALSE; + } + + if (file_inode && self->file_inode && file_inode != self->file_inode) + return FALSE; + + return TRUE; +} + +const char * +sysprof_elf_get_nick (SysprofElf *self) +{ + g_return_val_if_fail (SYSPROF_IS_ELF (self), NULL); + + return self->nick; +} diff --git a/src/libsysprof/sysprof-energy-usage.c b/src/libsysprof/sysprof-energy-usage.c new file mode 100644 index 00000000..6c4db985 --- /dev/null +++ b/src/libsysprof/sysprof-energy-usage.c @@ -0,0 +1,57 @@ +/* sysprof-energy-usage.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-proxied-instrument-private.h" +#include "sysprof-energy-usage.h" + +struct _SysprofEnergyUsage +{ + SysprofProxiedInstrument parent_instance; +}; + +struct _SysprofEnergyUsageClass +{ + SysprofProxiedInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofEnergyUsage, sysprof_energy_usage, SYSPROF_TYPE_PROXIED_INSTRUMENT) + +static void +sysprof_energy_usage_class_init (SysprofEnergyUsageClass *klass) +{ +} + +static void +sysprof_energy_usage_init (SysprofEnergyUsage *self) +{ + SYSPROF_PROXIED_INSTRUMENT (self)->call_stop_first = TRUE; +} + +SysprofInstrument * +sysprof_energy_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_ENERGY_USAGE, + "bus-type", G_BUS_TYPE_SYSTEM, + "bus-name", "org.gnome.Sysprof3", + "object-path", "/org/gnome/Sysprof3/RAPL", + NULL); +} diff --git a/src/libsysprof/sysprof-energy-usage.h b/src/libsysprof/sysprof-energy-usage.h new file mode 100644 index 00000000..211a2dfd --- /dev/null +++ b/src/libsysprof/sysprof-energy-usage.h @@ -0,0 +1,42 @@ +/* sysprof-energy-usage.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_ENERGY_USAGE (sysprof_energy_usage_get_type()) +#define SYSPROF_IS_ENERGY_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_ENERGY_USAGE) +#define SYSPROF_ENERGY_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_ENERGY_USAGE, SysprofEnergyUsage) +#define SYSPROF_ENERGY_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_ENERGY_USAGE, SysprofEnergyUsageClass) + +typedef struct _SysprofEnergyUsage SysprofEnergyUsage; +typedef struct _SysprofEnergyUsageClass SysprofEnergyUsageClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_energy_usage_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_energy_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofEnergyUsage, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-flatpak.c b/src/libsysprof/sysprof-flatpak.c deleted file mode 100644 index 378127c5..00000000 --- a/src/libsysprof/sysprof-flatpak.c +++ /dev/null @@ -1,158 +0,0 @@ -/* sysprof-flatpak.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "sysprof-flatpak.h" - -#define ETC_INSTALLATIONS_D "/etc/flatpak/installations.d" - -static void -add_from_installations_d (GPtrArray *ret, - const gchar *path, - const gchar *prefix) -{ - g_autoptr(GDir) dir = NULL; - - g_assert (ret != NULL); - g_assert (path != NULL); - - /* Now look at /etc/flatpak/installations.d for keyfiles with Path= */ - if ((dir = g_dir_open (path, 0, NULL))) - { - const gchar *name; - - while ((name = g_dir_read_name (dir))) - { - g_autofree gchar *key_path = g_build_filename (path, name, NULL); - g_autoptr(GKeyFile) kf = g_key_file_new (); - - if (g_key_file_load_from_file (kf, key_path, G_KEY_FILE_NONE, NULL)) - { - g_auto(GStrv) groups = g_key_file_get_groups (kf, NULL); - - for (guint i = 0; groups[i]; i++) - { - if (g_key_file_has_key (kf, groups[i], "Path", NULL)) - { - gchar *val = g_key_file_get_string (kf, groups[i], "Path", NULL); - - if (val) - { - if (prefix) - g_ptr_array_add (ret, g_build_filename (prefix, val, NULL)); - else - g_ptr_array_add (ret, g_steal_pointer (&val)); - } - } - } - } - } - } -} - -gchar ** -get_installations (void) -{ - GPtrArray *ret = g_ptr_array_new (); - - /* We might be running from a container, so ignore XDG_DATA_HOME as - * that will likely be different that what we care about the host. - * TODO: Can we find a way to support non-standard XDG_DATA_HOME? - */ - g_ptr_array_add (ret, g_build_filename (g_get_home_dir (), ".local", "share", "flatpak", NULL)); - g_ptr_array_add (ret, g_strdup ("/var/lib/flatpak")); - - add_from_installations_d (ret, ETC_INSTALLATIONS_D, NULL); - add_from_installations_d (ret, "/var/run/host" ETC_INSTALLATIONS_D, "/var/run/host"); - - g_ptr_array_add (ret, NULL); - return (gchar **)g_ptr_array_free (ret, FALSE); -} - -static void -get_arch (gchar *out, - gsize len) -{ - struct utsname u; - uname (&u); - g_strlcpy (out, u.machine, len); -} - -void -_sysprof_flatpak_debug_dirs (GPtrArray *dirs) -{ - g_auto(GStrv) installs = get_installations (); - gchar arch[32]; - - g_assert (dirs != NULL); - - get_arch (arch, sizeof arch); - - g_ptr_array_add (dirs, g_strdup ("/var/run/host/usr/lib/debug")); - g_ptr_array_add (dirs, g_strdup ("/var/run/host/usr/lib32/debug")); - g_ptr_array_add (dirs, g_strdup ("/var/run/host/usr/lib64/debug")); - - /* For each of the installations, we want to look at all of the runtimes that - * exist within it. Of those runtimes, we want to limit ourselves to the active - * version of each runtime, and see if we have a deployment for the current - * system arch that contains a "lib/debug" directory. We could add more, but - * it's just too many directories. - */ - - for (guint i = 0; installs[i]; i++) - { - g_autofree gchar *repo_dir = g_build_filename (installs[i], "runtime", NULL); - g_autoptr(GDir) dir = g_dir_open (repo_dir, 0, NULL); - const gchar *name; - - if (dir == NULL) - continue; - - while ((name = g_dir_read_name (dir))) - { - g_autofree gchar *version_dir = g_build_filename (installs[i], "runtime", name, arch, NULL); - g_autoptr(GDir) vdir = g_dir_open (version_dir, 0, NULL); - const gchar *version; - - if (vdir == NULL) - continue; - - while ((version = g_dir_read_name (vdir))) - { - g_autofree gchar *lib_debug = g_build_filename (version_dir, version, "active", "files", "lib", "debug", NULL); - - if (g_file_test (lib_debug, G_FILE_TEST_EXISTS)) - g_ptr_array_add (dirs, g_steal_pointer (&lib_debug)); - } - } - } -} - -gchar ** -sysprof_flatpak_debug_dirs (void) -{ - GPtrArray *dirs = g_ptr_array_new (); - _sysprof_flatpak_debug_dirs (dirs); - g_ptr_array_add (dirs, NULL); - return (gchar **)g_ptr_array_free (dirs, FALSE); -} diff --git a/src/libsysprof/sysprof-gjs-source.c b/src/libsysprof/sysprof-gjs-source.c deleted file mode 100644 index 168598dc..00000000 --- a/src/libsysprof/sysprof-gjs-source.c +++ /dev/null @@ -1,68 +0,0 @@ -/* sysprof-gjs-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-gjs-source" - -#include "config.h" - -#include "sysprof-gjs-source.h" - -struct _SysprofGjsSource -{ - SysprofTracefdSource parent_instance; -}; - -static SysprofSourceInterface *parent_iface; - -static void -sysprof_gjs_source_modify_spawn (SysprofSource *source, - SysprofSpawnable *spawnable) -{ - sysprof_spawnable_setenv (spawnable, "GJS_ENABLE_PROFILER", "1"); - parent_iface->modify_spawn (source, spawnable); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - parent_iface = g_type_interface_peek_parent (iface); - - iface->modify_spawn = sysprof_gjs_source_modify_spawn; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofGjsSource, sysprof_gjs_source, SYSPROF_TYPE_TRACEFD_SOURCE, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -sysprof_gjs_source_class_init (SysprofGjsSourceClass *klass) -{ -} - -static void -sysprof_gjs_source_init (SysprofGjsSource *self) -{ - sysprof_tracefd_source_set_envvar (SYSPROF_TRACEFD_SOURCE (self), "GJS_TRACE_FD"); -} - -SysprofSource * -sysprof_gjs_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_GJS_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-gjs-source.h b/src/libsysprof/sysprof-gjs-source.h deleted file mode 100644 index 02316d80..00000000 --- a/src/libsysprof/sysprof-gjs-source.h +++ /dev/null @@ -1,35 +0,0 @@ -/* sysprof-gjs-source.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-tracefd-source.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_GJS_SOURCE (sysprof_gjs_source_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofGjsSource, sysprof_gjs_source, SYSPROF, GJS_SOURCE, SysprofTracefdSource) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_gjs_source_new (void); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-governor-source.c b/src/libsysprof/sysprof-governor-source.c deleted file mode 100644 index e22953f2..00000000 --- a/src/libsysprof/sysprof-governor-source.c +++ /dev/null @@ -1,318 +0,0 @@ -/* sysprof-governor-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-governor-source" - -#include "config.h" - -#include "sysprof-governor-source.h" -#include "sysprof-helpers.h" - -struct _SysprofGovernorSource -{ - GObject parent_instance; - gchar *old_governor; - int old_paranoid; - guint disable_governor : 1; -}; - -enum { - PROP_0, - PROP_DISABLE_GOVERNOR, - N_PROPS -}; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofGovernorSource, sysprof_governor_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_governor_source_finalize (GObject *object) -{ - SysprofGovernorSource *self = (SysprofGovernorSource *)object; - - g_clear_pointer (&self->old_governor, g_free); - - G_OBJECT_CLASS (sysprof_governor_source_parent_class)->finalize (object); -} - -static void -sysprof_governor_source_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofGovernorSource *self = SYSPROF_GOVERNOR_SOURCE (object); - - switch (prop_id) - { - case PROP_DISABLE_GOVERNOR: - g_value_set_boolean (value, sysprof_governor_source_get_disable_governor (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_governor_source_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofGovernorSource *self = SYSPROF_GOVERNOR_SOURCE (object); - - switch (prop_id) - { - case PROP_DISABLE_GOVERNOR: - sysprof_governor_source_set_disable_governor (self, g_value_get_boolean (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_governor_source_class_init (SysprofGovernorSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_governor_source_finalize; - object_class->get_property = sysprof_governor_source_get_property; - object_class->set_property = sysprof_governor_source_set_property; - - properties [PROP_DISABLE_GOVERNOR] = - g_param_spec_boolean ("disable-governor", - "Disable Governor", - "Disable Governor", - TRUE, - (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_governor_source_init (SysprofGovernorSource *self) -{ - self->disable_governor = FALSE; - self->old_paranoid = 2; -} - -SysprofSource * -sysprof_governor_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_GOVERNOR_SOURCE, NULL); -} - -gboolean -sysprof_governor_source_get_disable_governor (SysprofGovernorSource *self) -{ - g_return_val_if_fail (SYSPROF_IS_GOVERNOR_SOURCE (self), FALSE); - - return self->disable_governor; -} - -void -sysprof_governor_source_set_disable_governor (SysprofGovernorSource *self, - gboolean disable_governor) -{ - g_return_if_fail (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - disable_governor = !!disable_governor; - - if (disable_governor != self->disable_governor) - { - self->disable_governor = disable_governor; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISABLE_GOVERNOR]); - } -} - -static void -sysprof_governor_source_serialize (SysprofSource *source, - GKeyFile *keyfile, - const gchar *group) -{ - SysprofGovernorSource *self = (SysprofGovernorSource *)source; - - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - g_assert (keyfile != NULL); - g_assert (group != NULL); - - g_key_file_set_boolean (keyfile, group, "disable-governor", self->disable_governor); -} - -static void -sysprof_governor_source_deserialize (SysprofSource *source, - GKeyFile *keyfile, - const gchar *group) -{ - SysprofGovernorSource *self = (SysprofGovernorSource *)source; - - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - g_assert (keyfile != NULL); - g_assert (group != NULL); - - sysprof_governor_source_set_disable_governor (self, - g_key_file_get_boolean (keyfile, group, "disable-governor", NULL)); -} - -static void -disable_governor_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofGovernorSource) self = user_data; - g_autoptr(GError) error = NULL; - g_autofree gchar *old_governor = NULL; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - if (!sysprof_helpers_set_governor_finish (helpers, result, &old_governor, &error)) - g_warning ("Failed to change governor: %s", error->message); - else - self->old_governor = g_steal_pointer (&old_governor); - - sysprof_source_emit_ready (SYSPROF_SOURCE (self)); -} - -static void -disable_paranoid_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofGovernorSource) self = user_data; - g_autoptr(GError) error = NULL; - int old_paranoid; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - if (!sysprof_helpers_set_paranoid_finish (helpers, result, &old_paranoid, &error)) - g_debug ("Failed to change perf_event_paranoid: %s", error->message); - else - self->old_paranoid = old_paranoid; - - if (!self->disable_governor) - sysprof_source_emit_ready (SYSPROF_SOURCE (self)); - else - sysprof_helpers_set_governor_async (helpers, - "performance", - NULL, - disable_governor_cb, - g_steal_pointer (&self)); -} - -static void -sysprof_governor_source_prepare (SysprofSource *source) -{ - SysprofGovernorSource *self = (SysprofGovernorSource *)source; - SysprofHelpers *helpers = sysprof_helpers_get_default (); - - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - sysprof_helpers_set_paranoid_async (helpers, - -1, - NULL, - disable_paranoid_cb, - g_object_ref (self)); -} - -static void -enable_governor_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofGovernorSource) self = user_data; - g_autoptr(GError) error = NULL; - g_autofree gchar *old_governor = NULL; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - if (!sysprof_helpers_set_governor_finish (helpers, result, &old_governor, &error)) - g_warning ("Failed to change governor: %s", error->message); - - g_clear_pointer (&self->old_governor, g_free); - - sysprof_source_emit_finished (SYSPROF_SOURCE (self)); -} - -static void -enable_paranoid_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofGovernorSource) self = user_data; - g_autoptr(GError) error = NULL; - int old_governor; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - if (!sysprof_helpers_set_paranoid_finish (helpers, result, &old_governor, &error)) - g_debug ("Failed to change event_perf_paranoid: %s", error->message); - - if (!self->disable_governor || self->old_governor == NULL) - sysprof_source_emit_finished (SYSPROF_SOURCE (self)); - else - sysprof_helpers_set_governor_async (helpers, - self->old_governor, - NULL, - enable_governor_cb, - g_object_ref (self)); -} - -static void -sysprof_governor_source_stop (SysprofSource *source) -{ - SysprofGovernorSource *self = (SysprofGovernorSource *)source; - SysprofHelpers *helpers = sysprof_helpers_get_default (); - - g_assert (SYSPROF_IS_GOVERNOR_SOURCE (self)); - - sysprof_helpers_set_paranoid_async (helpers, - self->old_paranoid, - NULL, - enable_paranoid_cb, - g_object_ref (self)); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->deserialize = sysprof_governor_source_deserialize; - iface->prepare = sysprof_governor_source_prepare; - iface->serialize = sysprof_governor_source_serialize; - iface->stop = sysprof_governor_source_stop; -} diff --git a/src/libsysprof/sysprof-helpers.c b/src/libsysprof/sysprof-helpers.c deleted file mode 100644 index 08810858..00000000 --- a/src/libsysprof/sysprof-helpers.c +++ /dev/null @@ -1,835 +0,0 @@ -/* sysprof-helpers.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-helpers" - -#include "config.h" - -#include - -#include "sysprof-helpers.h" -#include "sysprof-polkit-private.h" - -#include "helpers.h" -#include "ipc-service.h" - -struct _SysprofHelpers -{ - GObject parent_instance; - IpcService *proxy; - GQueue auth_tasks; - guint did_auth : 1; -}; - -G_DEFINE_TYPE (SysprofHelpers, sysprof_helpers, G_TYPE_OBJECT) - -static void -sysprof_helpers_finalize (GObject *object) -{ - SysprofHelpers *self = (SysprofHelpers *)object; - - g_clear_object (&self->proxy); - - G_OBJECT_CLASS (sysprof_helpers_parent_class)->finalize (object); -} - -static void -sysprof_helpers_class_init (SysprofHelpersClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_helpers_finalize; -} - -static void -sysprof_helpers_init (SysprofHelpers *self) -{ - g_autoptr(GDBusConnection) bus = NULL; - - if ((bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL))) - self->proxy = ipc_service_proxy_new_sync (bus, - G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION, - "org.gnome.Sysprof3", - "/org/gnome/Sysprof3", - NULL, NULL); -} - -SysprofHelpers * -sysprof_helpers_get_default (void) -{ - static SysprofHelpers *instance; - - if (g_once_init_enter (&instance)) - { - SysprofHelpers *self = g_object_new (SYSPROF_TYPE_HELPERS, NULL); - g_object_add_weak_pointer (G_OBJECT (self), (gpointer *)&instance); - g_once_init_leave (&instance, self); - } - - return instance; -} - -static gboolean -fail_if_no_proxy (SysprofHelpers *self, - GTask *task) -{ - g_assert (SYSPROF_IS_HELPERS (self)); - g_assert (G_IS_TASK (task)); - - if (self->proxy == NULL) - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_NOT_CONNECTED, - "No D-Bus proxy to communicate with daemon"); - return TRUE; - } - - return FALSE; -} - -static void -sysprof_helpers_list_processes_local_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(GTask) task = user_data; - g_autoptr(GError) error = NULL; - g_autofree gint32 *processes = NULL; - gsize n_processes = 0; - - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (helpers_list_processes_finish (result, &processes, &n_processes, &error)) - { - g_autoptr(GVariant) ret = NULL; - - ret = g_variant_new_fixed_array (G_VARIANT_TYPE_INT32, - processes, - n_processes, - sizeof (gint32)); - g_task_return_pointer (task, - g_variant_take_ref (g_steal_pointer (&ret)), - (GDestroyNotify) g_variant_unref); - - return; - } - - g_task_return_error (task, g_steal_pointer (&error)); -} - -static void -sysprof_helpers_list_processes_cb (IpcService *service, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(GTask) task = user_data; - g_autoptr(GVariant) processes = NULL; - g_autoptr(GError) error = NULL; - - g_assert (IPC_IS_SERVICE (service)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!ipc_service_call_list_processes_finish (service, &processes, result, &error)) - helpers_list_processes_async (g_task_get_cancellable (task), - sysprof_helpers_list_processes_local_cb, - g_object_ref (task)); - else - g_task_return_pointer (task, g_steal_pointer (&processes), (GDestroyNotify) g_variant_unref); -} - -gboolean -sysprof_helpers_list_processes (SysprofHelpers *self, - GCancellable *cancellable, - gint32 **processes, - gsize *n_processes, - GError **error) -{ - g_autoptr(GVariant) fixed_ar = NULL; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (processes != NULL, FALSE); - g_return_val_if_fail (n_processes != NULL, FALSE); - - if (helpers_can_see_pids ()) - { - /* No need to query remote if we can see pids in this namespace */ - if (helpers_list_processes (processes, n_processes)) - return TRUE; - } - - if (self->proxy && ipc_service_call_list_processes_sync (self->proxy, &fixed_ar, cancellable, NULL)) - { - const gint32 *data; - gsize len; - - data = g_variant_get_fixed_array (fixed_ar, &len, sizeof (gint32)); - *processes = g_memdup2 (data, len * sizeof (gint32)); - *n_processes = len; - - return TRUE; - } - - helpers_list_processes (processes, n_processes); - - return TRUE; -} - -void -sysprof_helpers_list_processes_async (SysprofHelpers *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_HELPERS (self)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_helpers_list_processes_async); - - if (self->proxy != NULL) - ipc_service_call_list_processes (self->proxy, - cancellable, - (GAsyncReadyCallback) sysprof_helpers_list_processes_cb, - g_steal_pointer (&task)); - else - helpers_list_processes_async (cancellable, - sysprof_helpers_list_processes_local_cb, - g_steal_pointer (&task)); -} - -gboolean -sysprof_helpers_list_processes_finish (SysprofHelpers *self, - GAsyncResult *result, - gint32 **processes, - gsize *n_processes, - GError **error) -{ - g_autoptr(GVariant) ret = NULL; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - if ((ret = g_task_propagate_pointer (G_TASK (result), error))) - { - const gint32 *p; - gsize n; - - p = g_variant_get_fixed_array (ret, &n, sizeof (gint32)); - - if (processes != NULL) - *processes = g_memdup2 (p, n * sizeof (gint32)); - - if (n_processes != NULL) - *n_processes = n; - - return TRUE; - } - - return FALSE; -} - -gboolean -sysprof_helpers_get_proc_fd (SysprofHelpers *self, - const gchar *path, - GCancellable *cancellable, - gint *out_fd, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (path != NULL, FALSE); - g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (out_fd != NULL, FALSE); - - *out_fd = -1; - - if (self->proxy != NULL) - { - g_autoptr(GVariant) reply = NULL; - g_autoptr(GUnixFDList) out_fd_list = NULL; - - reply = g_dbus_proxy_call_with_unix_fd_list_sync (G_DBUS_PROXY (self->proxy), - "GetProcFd", - g_variant_new ("(^ay)", path), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, - NULL, - &out_fd_list, - cancellable, - error); - - if (reply != NULL && out_fd_list != NULL) - { - gint handle = -1; - - g_variant_get (reply, "(h)", &handle); - - if (handle < g_unix_fd_list_get_length (out_fd_list)) - { - *out_fd = g_unix_fd_list_get (out_fd_list, handle, error); - return *out_fd != -1; - } - } - } - - if (!helpers_get_proc_fd (path, out_fd)) - return FALSE; - - g_clear_error (error); - return TRUE; -} - -static void -sysprof_helpers_get_proc_file_cb (IpcService *service, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(GTask) task = user_data; - g_autoptr(GError) error = NULL; - g_autofree gchar *contents = NULL; - - g_assert (IPC_IS_SERVICE (service)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!ipc_service_call_get_proc_file_finish (service, &contents, result, &error)) - { - const gchar *path = g_task_get_task_data (task); - gsize len; - - if (!helpers_get_proc_file (path, &contents, &len)) - { - g_task_return_error (task, g_steal_pointer (&error)); - return; - } - - g_clear_error (&error); - } - - g_task_return_pointer (task, g_steal_pointer (&contents), g_free); -} - -gboolean -sysprof_helpers_get_proc_file (SysprofHelpers *self, - const gchar *path, - GCancellable *cancellable, - gchar **contents, - GError **error) -{ - gsize len; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE); - - if (self->proxy != NULL) - { - if (ipc_service_call_get_proc_file_sync (self->proxy, path, contents, cancellable, error)) - return TRUE; - } - - if (!helpers_get_proc_file (path, contents, &len)) - return FALSE; - - if (error != NULL) - g_clear_error (error); - - return TRUE; -} - -void -sysprof_helpers_get_proc_file_async (SysprofHelpers *self, - const gchar *path, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_HELPERS (self)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_helpers_get_proc_file_async); - g_task_set_task_data (task, g_strdup (path), g_free); - - if (!fail_if_no_proxy (self, task)) - ipc_service_call_get_proc_file (self->proxy, - path, - cancellable, - (GAsyncReadyCallback) sysprof_helpers_get_proc_file_cb, - g_steal_pointer (&task)); -} - -gboolean -sysprof_helpers_get_proc_file_finish (SysprofHelpers *self, - GAsyncResult *result, - gchar **contents, - GError **error) -{ - g_autofree gchar *ret = NULL; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - if ((ret = g_task_propagate_pointer (G_TASK (result), error))) - { - if (contents != NULL) - *contents = g_steal_pointer (&ret); - return TRUE; - } - - return FALSE; -} - -#ifdef __linux__ -static GVariant * -build_options_dict (struct perf_event_attr *attr) -{ - return g_variant_take_ref ( - g_variant_new_parsed ("[" - "{'comm', <%b>}," -#ifdef HAVE_PERF_CLOCKID - "{'clockid', <%i>}," - "{'use_clockid', <%b>}," -#endif - "{'config', <%t>}," - "{'disabled', <%b>}," - "{'exclude_idle', <%b>}," - "{'mmap', <%b>}," - "{'wakeup_events', <%u>}," - "{'sample_id_all', <%b>}," - "{'sample_period', <%t>}," - "{'sample_type', <%t>}," - "{'task', <%b>}," - "{'type', <%u>}" - "]", - (gboolean)!!attr->comm, -#ifdef HAVE_PERF_CLOCKID - (gint32)attr->clockid, - (gboolean)!!attr->use_clockid, -#endif - (guint64)attr->config, - (gboolean)!!attr->disabled, - (gboolean)!!attr->exclude_idle, - (gboolean)!!attr->mmap, - (guint32)attr->wakeup_events, - (gboolean)!!attr->sample_id_all, - (guint64)attr->sample_period, - (guint64)attr->sample_type, - (gboolean)!!attr->task, - (guint32)attr->type)); -} - -gboolean -sysprof_helpers_perf_event_open (SysprofHelpers *self, - struct perf_event_attr *attr, - gint32 pid, - gint32 cpu, - gint32 group_fd, - guint64 flags, - GCancellable *cancellable, - gint *out_fd, - GError **error) -{ - g_autoptr(GUnixFDList) fd_list = NULL; - g_autoptr(GUnixFDList) out_fd_list = NULL; - g_autoptr(GVariant) options = NULL; - g_autoptr(GVariant) reply = NULL; - gint handle = -1; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE); - g_return_val_if_fail (group_fd >= -1, FALSE); - g_return_val_if_fail (out_fd != NULL, FALSE); - - *out_fd = -1; - - if (self->proxy == NULL) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_NOT_CONNECTED, - "No access to system proxy"); - return FALSE; - } - - if (group_fd != -1) - { - fd_list = g_unix_fd_list_new (); - handle = g_unix_fd_list_append (fd_list, group_fd, NULL); - } - - options = build_options_dict (attr); - - reply = g_dbus_proxy_call_with_unix_fd_list_sync (G_DBUS_PROXY (self->proxy), - "PerfEventOpen", - g_variant_new ("(@a{sv}iiht)", - options, - pid, - cpu, - handle, - flags), - G_DBUS_CALL_FLAGS_NONE, - -1, - fd_list, - &out_fd_list, - cancellable, - error); - - if (reply == NULL) - { - /* Try in-process (without elevated privs) */ - if (helpers_perf_event_open (options, pid, cpu, group_fd, flags, out_fd)) - { - g_clear_error (error); - return TRUE; - } - - return FALSE; - } - - if (out_fd_list == NULL || g_unix_fd_list_get_length (out_fd_list) != 1) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - "Received invalid reply from peer"); - return FALSE; - } - - *out_fd = g_unix_fd_list_get (out_fd_list, 0, error); - - return *out_fd != -1; -} -#endif /* __linux__ */ - -static void -sysprof_helpers_authorize_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(SysprofHelpers) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_HELPERS (self)); - - if (!_sysprof_polkit_authorize_for_bus_finish (result, &error)) - { - while (self->auth_tasks.length > 0) - { - g_autoptr(GTask) task = g_queue_pop_head (&self->auth_tasks); - g_task_return_error (task, g_error_copy (error)); - } - } - else - { - self->did_auth = TRUE; - while (self->auth_tasks.length > 0) - { - g_autoptr(GTask) task = g_queue_pop_head (&self->auth_tasks); - g_task_return_boolean (task, TRUE); - } - } -} - -static void -sysprof_helpers_do_auth (SysprofHelpers *self) -{ - GDBusConnection *bus; - - g_assert (SYSPROF_IS_HELPERS (self)); - - if (self->proxy == NULL || self->did_auth) - { - /* No D-Bus/Polkit? Bail early, fail sooner. If we already successfully - * did auth, then short circuit to avoid spamming the user. - */ - while (self->auth_tasks.length > 0) - { - g_autoptr(GTask) task = g_queue_pop_head (&self->auth_tasks); - g_task_return_boolean (task, TRUE); - } - - return; - } - - bus = g_dbus_proxy_get_connection (G_DBUS_PROXY (self->proxy)); - - _sysprof_polkit_authorize_for_bus_async (bus, - "org.gnome.sysprof3.profile", - NULL, - TRUE, - NULL, - sysprof_helpers_authorize_cb, - g_object_ref (self)); -} - -void -sysprof_helpers_authorize_async (SysprofHelpers *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_HELPERS (self)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_helpers_authorize_async); - - g_queue_push_tail (&self->auth_tasks, g_steal_pointer (&task)); - - if (self->auth_tasks.length == 1) - sysprof_helpers_do_auth (self); -} - -gboolean -sysprof_helpers_authorize_finish (SysprofHelpers *self, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); -} - -gboolean -sysprof_helpers_get_process_info (SysprofHelpers *self, - const gchar *attributes, - gboolean no_proxy, - GCancellable *cancellable, - GVariant **info, - GError **error) -{ - g_assert (SYSPROF_IS_HELPERS (self)); - g_assert (attributes != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - g_assert (info != NULL); - - if (no_proxy) - { - *info = helpers_get_process_info (attributes); - return TRUE; - } - - return ipc_service_call_get_process_info_sync (self->proxy, attributes, info, cancellable, error); -} - -static void -sysprof_helpers_get_process_info_cb (IpcService *service, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(GTask) task = user_data; - g_autoptr(GVariant) info = NULL; - g_autoptr(GError) error = NULL; - - g_assert (IPC_IS_SERVICE (service)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!ipc_service_call_get_process_info_finish (service, &info, result, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_pointer (task, g_steal_pointer (&info), (GDestroyNotify)g_variant_unref); -} - -void -sysprof_helpers_get_process_info_async (SysprofHelpers *self, - const gchar *attributes, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_assert (SYSPROF_IS_HELPERS (self)); - g_assert (attributes != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_helpers_get_process_info_async); - - ipc_service_call_get_process_info (self->proxy, - attributes, - cancellable, - (GAsyncReadyCallback) sysprof_helpers_get_process_info_cb, - g_steal_pointer (&task)); -} - -gboolean -sysprof_helpers_get_process_info_finish (SysprofHelpers *self, - GAsyncResult *result, - GVariant **info, - GError **error) -{ - g_autoptr(GVariant) ret = NULL; - - g_assert (SYSPROF_IS_HELPERS (self)); - g_assert (G_IS_TASK (result)); - - if ((ret = g_task_propagate_pointer (G_TASK (result), error))) - { - if (info) - *info = g_steal_pointer (&ret); - return TRUE; - } - - return FALSE; -} - -static void -sysprof_helpers_set_governor_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - IpcService *proxy = (IpcService *)object; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - gchar *old_governor = NULL; - - g_assert (IPC_IS_SERVICE (proxy)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!ipc_service_call_set_governor_finish (proxy, &old_governor, result, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_pointer (task, old_governor, g_free); -} - -void -sysprof_helpers_set_governor_async (SysprofHelpers *self, - const gchar *governor, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_HELPERS (self)); - g_return_if_fail (governor != NULL); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_helpers_set_governor_async); - - if (fail_if_no_proxy (self, task)) - return; - - ipc_service_call_set_governor (self->proxy, - governor, - cancellable, - sysprof_helpers_set_governor_cb, - g_steal_pointer (&task)); -} - -gboolean -sysprof_helpers_set_governor_finish (SysprofHelpers *self, - GAsyncResult *result, - gchar **old_governor, - GError **error) -{ - g_autofree gchar *ret = NULL; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - if ((ret = g_task_propagate_pointer (G_TASK (result), error))) - { - if (old_governor != NULL) - *old_governor = g_steal_pointer (&ret); - return TRUE; - } - - return FALSE; -} - -static void -sysprof_helpers_set_paranoid_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - IpcService *proxy = (IpcService *)object; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - int old_paranoid = G_MAXINT; - - g_assert (IPC_IS_SERVICE (proxy)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - if (!ipc_service_call_set_paranoid_finish (proxy, &old_paranoid, result, &error)) - g_task_return_error (task, g_steal_pointer (&error)); - else - g_task_return_int (task, old_paranoid); -} - -void -sysprof_helpers_set_paranoid_async (SysprofHelpers *self, - int paranoid, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_HELPERS (self)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_helpers_set_paranoid_async); - - if (fail_if_no_proxy (self, task)) - return; - - ipc_service_call_set_paranoid (self->proxy, - paranoid, - cancellable, - sysprof_helpers_set_paranoid_cb, - g_steal_pointer (&task)); -} - -gboolean -sysprof_helpers_set_paranoid_finish (SysprofHelpers *self, - GAsyncResult *result, - int *old_paranoid, - GError **error) -{ - g_autoptr(GError) local_error = NULL; - - g_return_val_if_fail (SYSPROF_IS_HELPERS (self), FALSE); - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - *old_paranoid = g_task_propagate_int (G_TASK (result), &local_error); - - if (local_error) - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - - return TRUE; -} diff --git a/src/libsysprof/sysprof-helpers.h b/src/libsysprof/sysprof-helpers.h deleted file mode 100644 index b0794bbc..00000000 --- a/src/libsysprof/sysprof-helpers.h +++ /dev/null @@ -1,121 +0,0 @@ -/* sysprof-helpers.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#ifdef __linux__ -# include -#endif - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_HELPERS (sysprof_helpers_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofHelpers, sysprof_helpers, SYSPROF, HELPERS, GObject) - -SysprofHelpers *sysprof_helpers_get_default (void); -void sysprof_helpers_authorize_async (SysprofHelpers *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_helpers_authorize_finish (SysprofHelpers *self, - GAsyncResult *result, - GError **error); -gboolean sysprof_helpers_list_processes (SysprofHelpers *self, - GCancellable *cancellable, - gint32 **processes, - gsize *n_processes, - GError **error); -void sysprof_helpers_list_processes_async (SysprofHelpers *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_helpers_list_processes_finish (SysprofHelpers *self, - GAsyncResult *result, - gint32 **processes, - gsize *n_processes, - GError **error); -gboolean sysprof_helpers_get_proc_fd (SysprofHelpers *self, - const gchar *path, - GCancellable *cancellable, - gint *out_fd, - GError **error); -gboolean sysprof_helpers_get_proc_file (SysprofHelpers *self, - const gchar *path, - GCancellable *cancellable, - gchar **contents, - GError **error); -void sysprof_helpers_get_proc_file_async (SysprofHelpers *self, - const gchar *path, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_helpers_get_proc_file_finish (SysprofHelpers *self, - GAsyncResult *result, - gchar **contents, - GError **error); -gboolean sysprof_helpers_get_process_info (SysprofHelpers *self, - const gchar *attributes, - gboolean no_proxy, - GCancellable *cancellable, - GVariant **info, - GError **error); -void sysprof_helpers_get_process_info_async (SysprofHelpers *self, - const gchar *attributes, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_helpers_get_process_info_finish(SysprofHelpers *self, - GAsyncResult *result, - GVariant **info, - GError **error); -#ifdef __linux__ -gboolean sysprof_helpers_perf_event_open (SysprofHelpers *self, - struct perf_event_attr *attr, - gint32 pid, - gint32 cpu, - gint32 group_fd, - guint64 flags, - GCancellable *cancellable, - gint *out_fd, - GError **error); -#endif -void sysprof_helpers_set_governor_async (SysprofHelpers *self, - const gchar *governor, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_helpers_set_governor_finish (SysprofHelpers *self, - GAsyncResult *result, - gchar **old_governor, - GError **error); -void sysprof_helpers_set_paranoid_async (SysprofHelpers *self, - int paranoid, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -gboolean sysprof_helpers_set_paranoid_finish (SysprofHelpers *self, - GAsyncResult *result, - int *old_paranoid, - GError **error); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-hostinfo-source.c b/src/libsysprof/sysprof-hostinfo-source.c deleted file mode 100644 index 5a7f8991..00000000 --- a/src/libsysprof/sysprof-hostinfo-source.c +++ /dev/null @@ -1,496 +0,0 @@ -/* sysprof-hostinfo-source.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sysprof-helpers.h" -#include "sysprof-hostinfo-source.h" - -#define PROC_STAT_BUF_SIZE 4096 - -struct _SysprofHostinfoSource -{ - GObject parent_instance; - - guint handler; - gint n_cpu; - gint stat_fd; - guint combined_id; - - GArray *freqs; - - SysprofCaptureWriter *writer; - GArray *cpu_info; - gchar *stat_buf; -}; - -typedef struct -{ - gint counter_base; - gdouble total; - glong last_user; - glong last_idle; - glong last_system; - glong last_nice; - glong last_iowait; - glong last_irq; - glong last_softirq; - glong last_steal; - glong last_guest; - glong last_guest_nice; -} CpuInfo; - -typedef struct -{ - gint stat_fd; - gint64 max; -} CpuFreq; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofHostinfoSource, sysprof_hostinfo_source, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -SysprofSource * -sysprof_hostinfo_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_HOSTINFO_SOURCE, NULL); -} - -static gboolean -read_stat (SysprofHostinfoSource *self) -{ - gssize len; - - g_assert (self != NULL); - g_assert (self->stat_fd != -1); - g_assert (self->stat_buf != NULL); - - if (lseek (self->stat_fd, 0, SEEK_SET) != 0) - return FALSE; - - len = read (self->stat_fd, self->stat_buf, PROC_STAT_BUF_SIZE); - if (len <= 0) - return FALSE; - - if (len < PROC_STAT_BUF_SIZE) - self->stat_buf[len] = 0; - else - self->stat_buf[PROC_STAT_BUF_SIZE-1] = 0; - - return TRUE; -} - -static void -poll_cpu (SysprofHostinfoSource *self) -{ - gchar cpu[64] = { 0 }; - glong user; - glong sys; - glong nice; - glong idle; - glong iowait; - glong irq; - glong softirq; - glong steal; - glong guest; - glong guest_nice; - glong user_calc; - glong system_calc; - glong nice_calc; - glong idle_calc; - glong iowait_calc; - glong irq_calc; - glong softirq_calc; - glong steal_calc; - glong guest_calc; - glong guest_nice_calc; - glong total; - gchar *line; - gint ret; - gint id; - - if (read_stat (self)) - { - line = self->stat_buf; - - for (gsize i = 0; self->stat_buf[i]; i++) - { - if (self->stat_buf[i] == '\n') - { - self->stat_buf[i] = '\0'; - - if (strncmp (line, "cpu", 3) == 0) - { - if (isdigit (line[3])) - { - CpuInfo *cpu_info; - - user = nice = sys = idle = id = 0; - ret = sscanf (line, "%s %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", - cpu, &user, &nice, &sys, &idle, - &iowait, &irq, &softirq, &steal, &guest, &guest_nice); - if (ret != 11) - goto next; - - ret = sscanf(cpu, "cpu%d", &id); - - if (ret != 1 || id < 0 || id >= self->n_cpu) - goto next; - - cpu_info = &g_array_index (self->cpu_info, CpuInfo, id); - - user_calc = user - cpu_info->last_user; - nice_calc = nice - cpu_info->last_nice; - system_calc = sys - cpu_info->last_system; - idle_calc = idle - cpu_info->last_idle; - iowait_calc = iowait - cpu_info->last_iowait; - irq_calc = irq - cpu_info->last_irq; - softirq_calc = softirq - cpu_info->last_softirq; - steal_calc = steal - cpu_info->last_steal; - guest_calc = guest - cpu_info->last_guest; - guest_nice_calc = guest_nice - cpu_info->last_guest_nice; - - total = user_calc + nice_calc + system_calc + idle_calc + iowait_calc + irq_calc + softirq_calc + steal_calc + guest_calc + guest_nice_calc; - cpu_info->total = ((total - idle_calc) / (gdouble)total) * 100.0; - - cpu_info->last_user = user; - cpu_info->last_nice = nice; - cpu_info->last_idle = idle; - cpu_info->last_system = sys; - cpu_info->last_iowait = iowait; - cpu_info->last_irq = irq; - cpu_info->last_softirq = softirq; - cpu_info->last_steal = steal; - cpu_info->last_guest = guest; - cpu_info->last_guest_nice = guest_nice; - } - } - else - { - /* CPU info comes first. Skip further lines. */ - break; - } - - next: - line = &self->stat_buf[i + 1]; - } - } - } -} - -static gdouble -get_cpu_freq (SysprofHostinfoSource *self, - guint cpu) -{ - const CpuFreq *freq; - - g_assert (SYSPROF_IS_HOSTINFO_SOURCE (self)); - g_assert (cpu < self->freqs->len); - - freq = &g_array_index (self->freqs, CpuFreq, cpu); - - if (freq->stat_fd > -1) - { - gchar buf[128]; - gssize len; - - lseek (freq->stat_fd, 0, SEEK_SET); - len = read (freq->stat_fd, buf, sizeof buf - 1); - - if (len > 0 && len < sizeof buf) - { - gint64 val; - - buf[len] = 0; - g_strstrip (buf); - val = g_ascii_strtoll (buf, NULL, 10); - - return (gdouble)val / (gdouble)freq->max * 100.0; - } - } - - return 0.0; -} - -static void -publish_cpu (SysprofHostinfoSource *self) -{ - SysprofCaptureCounterValue *counter_values; - guint *counter_ids; - glong total_usage = 0; - - counter_ids = alloca (sizeof *counter_ids * (self->n_cpu * 2 + 1)); - counter_values = alloca (sizeof *counter_values * (self->n_cpu * 2 + 1)); - - for (guint i = 0; i < self->n_cpu; i++) - { - CpuInfo *info = &g_array_index (self->cpu_info, CpuInfo, i); - SysprofCaptureCounterValue *value = &counter_values[i*2]; - guint *id = &counter_ids[i*2]; - - *id = info->counter_base; - value->vdbl = info->total; - - id++; - value++; - - *id = info->counter_base + 1; - value->vdbl = get_cpu_freq (self, i); - - total_usage += info->total; - } - - /* Add combined counter */ - counter_ids[self->n_cpu * 2] = self->combined_id; - counter_values[self->n_cpu * 2].vdbl = total_usage / (gdouble)self->n_cpu; - - sysprof_capture_writer_set_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - counter_ids, - counter_values, - self->n_cpu * 2 + 1); -} - -static gboolean -collect_hostinfo_cb (gpointer data) -{ - SysprofHostinfoSource *self = data; - - g_assert (SYSPROF_IS_HOSTINFO_SOURCE (self)); - - poll_cpu (self); - publish_cpu (self); - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_hostinfo_source_finalize (GObject *object) -{ - SysprofHostinfoSource *self = (SysprofHostinfoSource *)object; - - if (self->handler) - { - g_source_remove (self->handler); - self->handler = 0; - } - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - g_clear_pointer (&self->cpu_info, g_array_unref); - g_clear_pointer (&self->stat_buf, g_free); - g_clear_pointer (&self->freqs, g_array_unref); - - G_OBJECT_CLASS (sysprof_hostinfo_source_parent_class)->finalize (object); -} - -static void -sysprof_hostinfo_source_class_init (SysprofHostinfoSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_hostinfo_source_finalize; -} - -static void -sysprof_hostinfo_source_init (SysprofHostinfoSource *self) -{ - self->stat_fd = -1; - self->cpu_info = g_array_new (FALSE, TRUE, sizeof (CpuInfo)); - self->stat_buf = g_malloc (PROC_STAT_BUF_SIZE); - self->freqs = g_array_new (FALSE, FALSE, sizeof (CpuFreq)); -} - -static void -sysprof_hostinfo_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofHostinfoSource *self = (SysprofHostinfoSource *)source; - - g_assert (SYSPROF_IS_HOSTINFO_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -sysprof_hostinfo_source_start (SysprofSource *source) -{ - SysprofHostinfoSource *self = (SysprofHostinfoSource *)source; - - g_assert (SYSPROF_IS_HOSTINFO_SOURCE (self)); - - /* 20 samples per second */ - self->handler = g_timeout_add (1000/20, collect_hostinfo_cb, self); -} - -static void -sysprof_hostinfo_source_stop (SysprofSource *source) -{ - SysprofHostinfoSource *self = (SysprofHostinfoSource *)source; - - g_assert (SYSPROF_IS_HOSTINFO_SOURCE (self)); - - g_source_remove (self->handler); - self->handler = 0; - - if (self->stat_fd != -1) - { - close (self->stat_fd); - self->stat_fd = -1; - } - - for (guint i = 0; i < self->freqs->len; i++) - { - CpuFreq *freq = &g_array_index (self->freqs, CpuFreq, i); - - if (freq->stat_fd != -1) - close (freq->stat_fd); - } - - if (self->freqs->len > 0) - g_array_remove_range (self->freqs, 0, self->freqs->len); - - sysprof_source_emit_finished (SYSPROF_SOURCE (self)); -} - -static void -sysprof_hostinfo_source_prepare (SysprofSource *source) -{ - SysprofHostinfoSource *self = (SysprofHostinfoSource *)source; - SysprofCaptureCounter *counters; - SysprofCaptureCounter *combined; - gint cpuinfo_fd; - - g_assert (SYSPROF_IS_HOSTINFO_SOURCE (self)); - g_assert (self->writer != NULL); - - /* We can generally get this even in containers */ - if (-1 != (cpuinfo_fd = g_open ("/proc/cpuinfo", O_RDONLY))) - { - sysprof_capture_writer_add_file_fd (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - "/proc/cpuinfo", - cpuinfo_fd); - close (cpuinfo_fd); - } - - self->stat_fd = open ("/proc/stat", O_RDONLY); - self->n_cpu = g_get_num_processors (); - - g_array_set_size (self->cpu_info, 0); - - counters = alloca (sizeof *counters * (self->n_cpu * 2 + 1)); - - for (guint i = 0; i < self->n_cpu; i++) - { - g_autofree gchar *max_path = NULL; - g_autofree gchar *cur_path = NULL; - g_autofree gchar *maxstr = NULL; - SysprofCaptureCounter *ctr = &counters[i*2]; - CpuInfo info = { 0 }; - CpuFreq freq = { 0 }; - - /* - * Request 2 counter values. - * One for CPU and one for Frequency. - */ - info.counter_base = sysprof_capture_writer_request_counter (self->writer, 2); - - /* - * Define counters for capture file. - */ - ctr->id = info.counter_base; - ctr->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; - ctr->value.vdbl = 0; - g_strlcpy (ctr->category, "CPU Percent", sizeof ctr->category); - g_snprintf (ctr->name, sizeof ctr->name, "Total CPU %d", i); - g_snprintf (ctr->description, sizeof ctr->description, - "Total CPU usage %d", i); - - ctr++; - - max_path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_max_freq", i); - cur_path = g_strdup_printf ("/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq", i); - - if (g_file_get_contents (max_path, &maxstr, NULL, NULL)) - { - g_strstrip (maxstr); - freq.max = g_ascii_strtoll (maxstr, NULL, 10); - } - - freq.stat_fd = -1; - sysprof_helpers_get_proc_fd (sysprof_helpers_get_default (), - cur_path, NULL, &freq.stat_fd, NULL); - g_array_append_val (self->freqs, freq); - - ctr->id = info.counter_base + 1; - ctr->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; - ctr->value.vdbl = 0; - g_strlcpy (ctr->category, "CPU Frequency", sizeof ctr->category); - g_snprintf (ctr->name, sizeof ctr->name, "CPU %d", i); - g_snprintf (ctr->description, sizeof ctr->description, - "Frequency of CPU %d", i); - - g_array_append_val (self->cpu_info, info); - } - - /* Now add combined counter */ - self->combined_id = sysprof_capture_writer_request_counter (self->writer, 1); - combined = &counters[self->n_cpu * 2]; - combined->id = self->combined_id; - combined->type = SYSPROF_CAPTURE_COUNTER_DOUBLE; - combined->value.vdbl = 0; - g_strlcpy (combined->category, "CPU Percent", sizeof combined->category); - g_snprintf (combined->name, sizeof combined->name, "Combined"); - g_snprintf (combined->description, sizeof combined->description, "Combined CPU usage"); - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - counters, - self->n_cpu * 2 + 1); - - sysprof_source_emit_ready (SYSPROF_SOURCE (self)); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->set_writer = sysprof_hostinfo_source_set_writer; - iface->prepare = sysprof_hostinfo_source_prepare; - iface->start = sysprof_hostinfo_source_start; - iface->stop = sysprof_hostinfo_source_stop; -} diff --git a/src/libsysprof/sysprof-instrument-private.h b/src/libsysprof/sysprof-instrument-private.h new file mode 100644 index 00000000..9474ab9c --- /dev/null +++ b/src/libsysprof/sysprof-instrument-private.h @@ -0,0 +1,67 @@ +/* sysprof-instrument-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-instrument.h" +#include "sysprof-recording.h" + +G_BEGIN_DECLS + +#define SYSPROF_INSTRUMENT_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS(obj, SYSPROF_TYPE_INSTRUMENT, SysprofInstrumentClass) + +struct _SysprofInstrument +{ + GObject parent; +}; + +struct _SysprofInstrumentClass +{ + GObjectClass parent_class; + + char **(*list_required_policy) (SysprofInstrument *self); + DexFuture *(*prepare) (SysprofInstrument *self, + SysprofRecording *recording); + DexFuture *(*record) (SysprofInstrument *self, + SysprofRecording *recording, + GCancellable *cancellable); + DexFuture *(*augment) (SysprofInstrument *self, + SysprofRecording *recording); + DexFuture *(*process_started) (SysprofInstrument *self, + SysprofRecording *recording, + int pid); +}; + +DexFuture *_sysprof_instruments_acquire_policy (GPtrArray *instruments, + SysprofRecording *recording); +DexFuture *_sysprof_instruments_prepare (GPtrArray *instruments, + SysprofRecording *recording); +DexFuture *_sysprof_instruments_record (GPtrArray *instruments, + SysprofRecording *recording, + GCancellable *cancellable); +DexFuture *_sysprof_instruments_augment (GPtrArray *instruments, + SysprofRecording *recording); +DexFuture *_sysprof_instruments_process_started (GPtrArray *instruments, + SysprofRecording *recording, + int pid); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-instrument.c b/src/libsysprof/sysprof-instrument.c new file mode 100644 index 00000000..e3254c90 --- /dev/null +++ b/src/libsysprof/sysprof-instrument.c @@ -0,0 +1,311 @@ +/* sysprof-instrument.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-instrument-private.h" + +#if HAVE_POLKIT +# include "sysprof-polkit-private.h" +#endif + +G_DEFINE_ABSTRACT_TYPE (SysprofInstrument, sysprof_instrument, G_TYPE_OBJECT) + +static char ** +sysprof_instrument_real_list_required_policy (SysprofInstrument *instrument) +{ + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + + return NULL; +} + +static DexFuture * +sysprof_instrument_real_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_instrument_real_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + return dex_future_new_for_boolean (TRUE); +} + +static void +sysprof_instrument_class_init (SysprofInstrumentClass *klass) +{ + klass->list_required_policy = sysprof_instrument_real_list_required_policy; + klass->prepare = sysprof_instrument_real_prepare; + klass->record = sysprof_instrument_real_record; +} + +static void +sysprof_instrument_init (SysprofInstrument *self) +{ +} + +static char ** +_sysprof_instrument_list_required_policy (SysprofInstrument *self) +{ + g_assert (SYSPROF_IS_INSTRUMENT (self)); + + if (SYSPROF_INSTRUMENT_GET_CLASS (self)->list_required_policy) + return SYSPROF_INSTRUMENT_GET_CLASS (self)->list_required_policy (self); + + return NULL; +} + +static DexFuture * +_sysprof_instrument_prepare (SysprofInstrument *self, + SysprofRecording *recording) +{ + g_assert (SYSPROF_IS_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if (SYSPROF_INSTRUMENT_GET_CLASS (self)->prepare) + return SYSPROF_INSTRUMENT_GET_CLASS (self)->prepare (self, recording); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +_sysprof_instrument_record (SysprofInstrument *self, + SysprofRecording *recording, + GCancellable *cancellable) +{ + g_assert (SYSPROF_IS_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + if (SYSPROF_INSTRUMENT_GET_CLASS (self)->record) + return SYSPROF_INSTRUMENT_GET_CLASS (self)->record (self, recording, cancellable); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +_sysprof_instrument_augment (SysprofInstrument *self, + SysprofRecording *recording) +{ + g_assert (SYSPROF_IS_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if (SYSPROF_INSTRUMENT_GET_CLASS (self)->augment) + return SYSPROF_INSTRUMENT_GET_CLASS (self)->augment (self, recording); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +_sysprof_instrument_process_started (SysprofInstrument *self, + SysprofRecording *recording, + int pid) +{ + g_assert (SYSPROF_IS_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if (SYSPROF_INSTRUMENT_GET_CLASS (self)->process_started) + return SYSPROF_INSTRUMENT_GET_CLASS (self)->process_started (self, recording, pid); + + return dex_future_new_for_boolean (TRUE); +} + +static char ** +_sysprof_instruments_list_required_policy (GPtrArray *instruments) +{ + g_autoptr(GPtrArray) all_policy = NULL; + + g_return_val_if_fail (instruments != NULL, NULL); + + all_policy = g_ptr_array_new_null_terminated (0, g_free, TRUE); + + for (guint i = 0; i < instruments->len; i++) + { + SysprofInstrument *instrument = g_ptr_array_index (instruments, i); + g_auto(GStrv) policy = _sysprof_instrument_list_required_policy (instrument); + + if (policy == NULL || policy[0] == NULL) + continue; + + for (guint j = 0; policy[j]; j++) + { + gboolean found = FALSE; + + for (guint k = 0; !found && k < all_policy->len; k++) + found = strcmp (policy[j], g_ptr_array_index (all_policy, k)) == 0; + + if (!found) + g_ptr_array_add (all_policy, g_strdup (policy[j])); + } + } + + if (all_policy->len == 0) + return NULL; + + return (char **)g_ptr_array_free (g_steal_pointer (&all_policy), FALSE); +} + +DexFuture * +_sysprof_instruments_acquire_policy (GPtrArray *instruments, + SysprofRecording *recording) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(PolkitDetails) details = NULL; + g_autoptr(GError) error = NULL; + g_auto(GStrv) required_policy = NULL; + + g_return_val_if_fail (instruments != NULL, NULL); + g_return_val_if_fail (SYSPROF_IS_RECORDING (recording), NULL); + + /* Ensure we have access to the System D-Bus so that we can get + * access to sysprofd for system information. + */ + if (!(connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* First ensure that all our required policy have been acquired on + * the bus so that we don't need to individually acquire them from + * each of the instruments. + */ + if ((required_policy = _sysprof_instruments_list_required_policy (instruments))) + { +#if HAVE_POLKIT + for (guint i = 0; required_policy[i]; i++) + { + if (!dex_await_boolean (_sysprof_polkit_authorize (connection, + required_policy[i], + details, + TRUE), &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + } +#endif + } + + return dex_future_new_for_boolean (TRUE); +} + +DexFuture * +_sysprof_instruments_prepare (GPtrArray *instruments, + SysprofRecording *recording) +{ + g_autoptr(GPtrArray) futures = NULL; + + g_return_val_if_fail (instruments != NULL, NULL); + g_return_val_if_fail (SYSPROF_IS_RECORDING (recording), NULL); + + futures = g_ptr_array_new_with_free_func (dex_unref); + + for (guint i = 0; i < instruments->len; i++) + { + SysprofInstrument *instrument = g_ptr_array_index (instruments, i); + + g_ptr_array_add (futures, _sysprof_instrument_prepare (instrument, recording)); + } + + if (futures->len == 0) + return dex_future_new_for_boolean (TRUE); + + return dex_future_allv ((DexFuture **)futures->pdata, futures->len); +} + +DexFuture * +_sysprof_instruments_record (GPtrArray *instruments, + SysprofRecording *recording, + GCancellable *cancellable) +{ + g_autoptr(GPtrArray) futures = NULL; + + g_return_val_if_fail (instruments != NULL, NULL); + g_return_val_if_fail (SYSPROF_IS_RECORDING (recording), NULL); + g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL); + + futures = g_ptr_array_new_with_free_func (dex_unref); + + for (guint i = 0; i < instruments->len; i++) + { + SysprofInstrument *instrument = g_ptr_array_index (instruments, i); + + g_ptr_array_add (futures, _sysprof_instrument_record (instrument, recording, cancellable)); + } + + if (futures->len == 0) + return dex_future_new_for_boolean (TRUE); + + return dex_future_allv ((DexFuture **)futures->pdata, futures->len); +} + +DexFuture * +_sysprof_instruments_augment (GPtrArray *instruments, + SysprofRecording *recording) +{ + g_autoptr(GPtrArray) futures = NULL; + + g_return_val_if_fail (instruments != NULL, NULL); + g_return_val_if_fail (SYSPROF_IS_RECORDING (recording), NULL); + + futures = g_ptr_array_new_with_free_func (dex_unref); + + for (guint i = 0; i < instruments->len; i++) + { + SysprofInstrument *instrument = g_ptr_array_index (instruments, i); + + g_ptr_array_add (futures, _sysprof_instrument_augment (instrument, recording)); + } + + if (futures->len == 0) + return dex_future_new_for_boolean (TRUE); + + return dex_future_allv ((DexFuture **)futures->pdata, futures->len); +} + +DexFuture * +_sysprof_instruments_process_started (GPtrArray *instruments, + SysprofRecording *recording, + int pid) +{ + g_autoptr(GPtrArray) futures = NULL; + + g_return_val_if_fail (instruments != NULL, NULL); + g_return_val_if_fail (SYSPROF_IS_RECORDING (recording), NULL); + + futures = g_ptr_array_new_with_free_func (dex_unref); + + for (guint i = 0; i < instruments->len; i++) + { + SysprofInstrument *instrument = g_ptr_array_index (instruments, i); + + g_ptr_array_add (futures, _sysprof_instrument_process_started (instrument, recording, pid)); + } + + if (futures->len == 0) + return dex_future_new_for_boolean (TRUE); + + return dex_future_allv ((DexFuture **)futures->pdata, futures->len); +} diff --git a/src/libsysprof/sysprof-instrument.h b/src/libsysprof/sysprof-instrument.h new file mode 100644 index 00000000..ed2f6980 --- /dev/null +++ b/src/libsysprof/sysprof-instrument.h @@ -0,0 +1,42 @@ +/* sysprof-instrument.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_INSTRUMENT (sysprof_instrument_get_type()) +#define SYSPROF_IS_INSTRUMENT(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_INSTRUMENT) +#define SYSPROF_INSTRUMENT(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_INSTRUMENT, SysprofInstrument) +#define SYSPROF_INSTRUMENT_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_INSTRUMENT, SysprofInstrumentClass) + +typedef struct _SysprofInstrument SysprofInstrument; +typedef struct _SysprofInstrumentClass SysprofInstrumentClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_instrument_get_type (void) G_GNUC_CONST; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofInstrument, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-jitmap-symbol-resolver.c b/src/libsysprof/sysprof-jitmap-symbol-resolver.c deleted file mode 100644 index 2015eb50..00000000 --- a/src/libsysprof/sysprof-jitmap-symbol-resolver.c +++ /dev/null @@ -1,125 +0,0 @@ -/* sysprof-jitmap-symbol-resolver.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-kernel-symbol.h" -#include "sysprof-jitmap-symbol-resolver.h" - -struct _SysprofJitmapSymbolResolver -{ - GObject parent_instance; - GHashTable *jitmap; -}; - -static void symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofJitmapSymbolResolver, - sysprof_jitmap_symbol_resolver, - G_TYPE_OBJECT, - 0, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SYMBOL_RESOLVER, - symbol_resolver_iface_init)) - -static void -sysprof_jitmap_symbol_resolver_finalize (GObject *object) -{ - SysprofJitmapSymbolResolver *self = (SysprofJitmapSymbolResolver *)object; - - g_clear_pointer (&self->jitmap, g_hash_table_unref); - - G_OBJECT_CLASS (sysprof_jitmap_symbol_resolver_parent_class)->finalize (object); -} - -static void -sysprof_jitmap_symbol_resolver_class_init (SysprofJitmapSymbolResolverClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_jitmap_symbol_resolver_finalize; -} - -static void -sysprof_jitmap_symbol_resolver_init (SysprofJitmapSymbolResolver *self) -{ - self->jitmap = g_hash_table_new_full (NULL, NULL, NULL, g_free); -} - -static void -sysprof_jitmap_symbol_resolver_load (SysprofSymbolResolver *resolver, - SysprofCaptureReader *reader) -{ - SysprofJitmapSymbolResolver *self = (SysprofJitmapSymbolResolver *)resolver; - SysprofCaptureFrameType type; - - g_assert (SYSPROF_IS_JITMAP_SYMBOL_RESOLVER (self)); - g_assert (reader != NULL); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - const SysprofCaptureJitmap *jitmap; - SysprofCaptureJitmapIter iter; - SysprofCaptureAddress addr; - const gchar *str; - - if (type != SYSPROF_CAPTURE_FRAME_JITMAP) - { - if (!sysprof_capture_reader_skip (reader)) - return; - continue; - } - - if (!(jitmap = sysprof_capture_reader_read_jitmap (reader))) - return; - - sysprof_capture_jitmap_iter_init (&iter, jitmap); - while (sysprof_capture_jitmap_iter_next (&iter, &addr, &str)) - g_hash_table_insert (self->jitmap, GSIZE_TO_POINTER (addr), g_strdup (str)); - } -} - -static gchar * -sysprof_jitmap_symbol_resolver_resolve (SysprofSymbolResolver *resolver, - guint64 time, - GPid pid, - SysprofCaptureAddress address, - GQuark *tag) -{ - SysprofJitmapSymbolResolver *self = (SysprofJitmapSymbolResolver *)resolver; - - g_assert (SYSPROF_IS_JITMAP_SYMBOL_RESOLVER (self)); - - *tag = 0; - - return g_strdup (g_hash_table_lookup (self->jitmap, GSIZE_TO_POINTER (address))); -} - -static void -symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface) -{ - iface->load = sysprof_jitmap_symbol_resolver_load; - iface->resolve = sysprof_jitmap_symbol_resolver_resolve; -} - -SysprofSymbolResolver * -sysprof_jitmap_symbol_resolver_new (void) -{ - return g_object_new (SYSPROF_TYPE_JITMAP_SYMBOL_RESOLVER, NULL); -} diff --git a/src/libsysprof/sysprof-jitmap-symbol-resolver.h b/src/libsysprof/sysprof-jitmap-symbol-resolver.h deleted file mode 100644 index 0d366194..00000000 --- a/src/libsysprof/sysprof-jitmap-symbol-resolver.h +++ /dev/null @@ -1,38 +0,0 @@ -/* sysprof-jitmap-symbol-resolver.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-symbol-resolver.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_JITMAP_SYMBOL_RESOLVER (sysprof_jitmap_symbol_resolver_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofJitmapSymbolResolver, sysprof_jitmap_symbol_resolver, SYSPROF, JITMAP_SYMBOL_RESOLVER, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSymbolResolver *sysprof_jitmap_symbol_resolver_new (void); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-jitmap-symbolizer.c b/src/libsysprof/sysprof-jitmap-symbolizer.c new file mode 100644 index 00000000..ff866274 --- /dev/null +++ b/src/libsysprof/sysprof-jitmap-symbolizer.c @@ -0,0 +1,251 @@ +/* sysprof-jitmap-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-document-jitmap.h" +#include "sysprof-document-private.h" +#include "sysprof-jitmap-symbolizer.h" +#include "sysprof-symbol-private.h" +#include "sysprof-symbolizer-private.h" + +typedef struct _Jitmap +{ + SysprofAddress address; + GRefString *name; +} Jitmap; + +struct _SysprofJitmapSymbolizer +{ + SysprofSymbolizer parent_instance; + GArray *jitmaps; +}; + +struct _SysprofJitmapSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofJitmapSymbolizer, sysprof_jitmap_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +typedef struct +{ + SysprofDocument *document; + GListModel *model; +} Prepare; + +static void +prepare_free (gpointer data) +{ + Prepare *prepare = data; + + g_clear_object (&prepare->model); + g_clear_object (&prepare->document); + g_free (prepare); +} + +static void +jitmap_clear (gpointer data) +{ + Jitmap *j = data; + g_clear_pointer (&j->name, g_ref_string_release); +} + +static int +compare_by_address (gconstpointer a, + gconstpointer b) +{ + const Jitmap *jitmap_a = a; + const Jitmap *jitmap_b = b; + + if (jitmap_a->address < jitmap_b->address) + return -1; + else if (jitmap_a->address > jitmap_b->address) + return 1; + else + return 0; +} + +static void +sysprof_jitmap_symbolizer_prepare_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofJitmapSymbolizer *self = source_object; + Prepare *prepare = task_data; + guint n_jitmaps; + + g_assert (G_IS_TASK (task)); + g_assert (SYSPROF_IS_JITMAP_SYMBOLIZER (self)); + g_assert (prepare != NULL); + g_assert (SYSPROF_IS_DOCUMENT (prepare->document)); + g_assert (G_IS_LIST_MODEL (prepare->model)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + n_jitmaps = g_list_model_get_n_items (prepare->model); + + for (guint i = 0; i < n_jitmaps; i++) + { + g_autoptr(SysprofDocumentJitmap) jitmap = g_list_model_get_item (prepare->model, i); + guint size = sysprof_document_jitmap_get_size (jitmap); + + for (guint j = 0; j < size; j++) + { + const char *name; + Jitmap map; + + if (!(name = sysprof_document_jitmap_get_mapping (jitmap, j, &map.address))) + continue; + + map.name = _sysprof_document_ref_string (prepare->document, name); + g_array_append_val (self->jitmaps, map); + } + } + + g_array_sort (self->jitmaps, compare_by_address); + + g_task_return_boolean (task, TRUE); +} + +static void +sysprof_jitmap_symbolizer_prepare_async (SysprofSymbolizer *symbolizer, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SysprofJitmapSymbolizer *self = (SysprofJitmapSymbolizer *)symbolizer; + g_autoptr(GTask) task = NULL; + Prepare *prepare; + + g_assert (SYSPROF_IS_JITMAP_SYMBOLIZER (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + prepare = g_new0 (Prepare, 1); + prepare->document = g_object_ref (document); + prepare->model = sysprof_document_list_jitmaps (document); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_jitmap_symbolizer_prepare_async); + g_task_set_task_data (task, prepare, prepare_free); + g_task_run_in_thread (task, sysprof_jitmap_symbolizer_prepare_worker); +} + +static gboolean +sysprof_jitmap_symbolizer_prepare_finish (SysprofSymbolizer *symbolizer, + GAsyncResult *result, + GError **error) +{ + g_assert (SYSPROF_IS_JITMAP_SYMBOLIZER (symbolizer)); + g_assert (G_IS_TASK (result)); + g_assert (g_task_is_valid (result, symbolizer)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static SysprofSymbol * +sysprof_jitmap_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofJitmapSymbolizer *self = (SysprofJitmapSymbolizer *)symbolizer; + const Jitmap key = { address, NULL }; + guint guess = (address & 0xFFFF) - 1; + const Jitmap *match; + + if (context != SYSPROF_ADDRESS_CONTEXT_NONE && + context != SYSPROF_ADDRESS_CONTEXT_USER) + return NULL; + + if ((address & 0xFFFFFFFF00000000) != 0xE000000000000000) + return NULL; + + /* Jitmap addresses generally start at 1 and work their way up + * monotonically (after masking off the high 0xE............... + * bits). So we can try for a fast index lookup to skip any sort + * of searching in the well behaved case. + */ + if G_LIKELY (guess < self->jitmaps->len) + { + match = &g_array_index (self->jitmaps, Jitmap, guess); + + if G_LIKELY (match->address == address) + goto create_symbol; + } + + match = bsearch (&key, + self->jitmaps->data, + self->jitmaps->len, + sizeof (Jitmap), + compare_by_address); + + if (match == NULL) + return NULL; + +create_symbol: + return _sysprof_symbol_new (g_ref_string_acquire (match->name), + NULL, + NULL, + match->address, + match->address + 1, + SYSPROF_SYMBOL_KIND_USER); +} + +static void +sysprof_jitmap_symbolizer_finalize (GObject *object) +{ + SysprofJitmapSymbolizer *self = (SysprofJitmapSymbolizer *)object; + + g_clear_pointer (&self->jitmaps, g_array_unref); + + G_OBJECT_CLASS (sysprof_jitmap_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_jitmap_symbolizer_class_init (SysprofJitmapSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->finalize = sysprof_jitmap_symbolizer_finalize; + + symbolizer_class->prepare_async = sysprof_jitmap_symbolizer_prepare_async; + symbolizer_class->prepare_finish = sysprof_jitmap_symbolizer_prepare_finish; + symbolizer_class->symbolize = sysprof_jitmap_symbolizer_symbolize; +} + +static void +sysprof_jitmap_symbolizer_init (SysprofJitmapSymbolizer *self) +{ + self->jitmaps = g_array_new (FALSE, FALSE, sizeof (Jitmap)); + g_array_set_clear_func (self->jitmaps, jitmap_clear); +} + +SysprofSymbolizer * +sysprof_jitmap_symbolizer_new (void) +{ + return g_object_new (SYSPROF_TYPE_JITMAP_SYMBOLIZER, NULL); +} diff --git a/src/libsysprof/sysprof-jitmap-symbolizer.h b/src/libsysprof/sysprof-jitmap-symbolizer.h new file mode 100644 index 00000000..5751eb46 --- /dev/null +++ b/src/libsysprof/sysprof-jitmap-symbolizer.h @@ -0,0 +1,42 @@ +/* sysprof-jitmap-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_JITMAP_SYMBOLIZER (sysprof_jitmap_symbolizer_get_type()) +#define SYSPROF_IS_JITMAP_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_JITMAP_SYMBOLIZER) +#define SYSPROF_JITMAP_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_JITMAP_SYMBOLIZER, SysprofJitmapSymbolizer) +#define SYSPROF_JITMAP_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_JITMAP_SYMBOLIZER, SysprofJitmapSymbolizerClass) + +typedef struct _SysprofJitmapSymbolizer SysprofJitmapSymbolizer; +typedef struct _SysprofJitmapSymbolizerClass SysprofJitmapSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_jitmap_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_jitmap_symbolizer_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofJitmapSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-journald-source.c b/src/libsysprof/sysprof-journald-source.c new file mode 100644 index 00000000..fad3199b --- /dev/null +++ b/src/libsysprof/sysprof-journald-source.c @@ -0,0 +1,107 @@ +/* sysprof-journald-source.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-journald-source.h" + +typedef struct _SysprofJournaldSource +{ + GSource parent_instance; + sd_journal *journal; + SysprofJournaldSourceFunc func; + gpointer func_data; + GDestroyNotify func_data_destroy; +} SysprofJournaldSource; + +static void +sysprof_journald_source_finalize (GSource *source) +{ + SysprofJournaldSource *self = (SysprofJournaldSource *)source; + + if (self->func_data_destroy) + self->func_data_destroy (self->func_data); + + g_clear_pointer (&self->journal, sd_journal_close); +} + +static gboolean +sysprof_journald_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + SysprofJournaldSource *self = (SysprofJournaldSource *)source; + + if (sd_journal_process (self->journal) == SD_JOURNAL_APPEND) + { + if (sd_journal_next (self->journal) > 0) + return self->func (self->func_data, self->journal); + } + + return G_SOURCE_CONTINUE; +} + +static GSourceFuncs sysprof_journald_source_funcs = { + .dispatch = sysprof_journald_source_dispatch, + .finalize = sysprof_journald_source_finalize, +}; + +GSource * +sysprof_journald_source_new (SysprofJournaldSourceFunc func, + gpointer func_data, + GDestroyNotify func_data_destroy) +{ + SysprofJournaldSource *self; + sd_journal *journal = NULL; + int fd; + + if (sd_journal_open (&journal, 0) < 0) + return NULL; + + if (sd_journal_seek_tail (journal) < 0) + goto failure; + + if (sd_journal_previous (journal) < 0) + goto failure; + + if (sd_journal_next (journal) < 0) + goto failure; + + if (-1 == (fd = sd_journal_get_fd (journal))) + goto failure; + + self = (SysprofJournaldSource *)g_source_new (&sysprof_journald_source_funcs, + sizeof (SysprofJournaldSource)); + self->journal = journal; + self->func = func; + self->func_data = func_data; + self->func_data_destroy = func_data_destroy; + + g_source_add_unix_fd ((GSource *)self, fd, sd_journal_get_events (journal)); + + return (GSource *)self; + +failure: + g_clear_pointer (&journal, sd_journal_close); + + return NULL; +} diff --git a/src/libsysprof/sysprof-journald-source.h b/src/libsysprof/sysprof-journald-source.h new file mode 100644 index 00000000..5e4552e9 --- /dev/null +++ b/src/libsysprof/sysprof-journald-source.h @@ -0,0 +1,36 @@ +/* sysprof-journald-source.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +typedef gboolean (*SysprofJournaldSourceFunc) (gpointer user_data, + sd_journal *journal); + +GSource *sysprof_journald_source_new (SysprofJournaldSourceFunc func, + gpointer func_data, + GDestroyNotify func_data_destroy); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-kallsyms-symbolizer.c b/src/libsysprof/sysprof-kallsyms-symbolizer.c new file mode 100644 index 00000000..c3241b32 --- /dev/null +++ b/src/libsysprof/sysprof-kallsyms-symbolizer.c @@ -0,0 +1,373 @@ +/* sysprof-kallsyms-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-kallsyms-symbolizer.h" +#include "sysprof-document-private.h" +#include "sysprof-strings-private.h" +#include "sysprof-symbolizer-private.h" +#include "sysprof-symbol-private.h" + +#define LAST_SYMBOL_LEN 0xffff + +typedef struct _KernelSymbol +{ + guint64 address; + GRefString *name; +} KernelSymbol; + +struct _SysprofKallsymsSymbolizer +{ + SysprofSymbolizer parent_instance; + GInputStream *stream; + GArray *kallsyms; + guint64 low; + guint64 high; +}; + +struct _SysprofKallsymsSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofKallsymsSymbolizer, sysprof_kallsyms_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +static SysprofStrings *kallsym_strings; +static GRefString *linux_string; + +static void +kernel_symbol_clear (gpointer data) +{ + KernelSymbol *symbol = data; + + g_clear_pointer (&symbol->name, g_ref_string_release); +} + +static inline void +sysprof_kallsyms_symbolizer_add (SysprofKallsymsSymbolizer *self, + guint64 address, + guint8 type, + const char *name) +{ + const KernelSymbol s = { address, sysprof_strings_get (kallsym_strings, name) }; + g_array_append_val (self->kallsyms, s); +} + +static inline int +sort_by_address (gconstpointer a, + gconstpointer b) +{ + const KernelSymbol *sym_a = a; + const KernelSymbol *sym_b = b; + + if (sym_a->address < sym_b->address) + return -1; + else if (sym_a->address > sym_b->address) + return 1; + else + return 0; +} + +static void +sysprof_kallsyms_symbolizer_prepare_worker (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + SysprofKallsymsSymbolizer *self = source_object; + GDataInputStream *input = task_data; + g_autoptr(GError) error = NULL; + guint64 last_address = 0; + char *line; + gsize len; + + g_assert (G_IS_TASK (task)); + g_assert (SYSPROF_IS_KALLSYMS_SYMBOLIZER (self)); + g_assert (G_IS_DATA_INPUT_STREAM (input)); + + while ((line = g_data_input_stream_read_line_utf8 (input, &len, cancellable, &error))) + { + const char *endptr = &line[len]; + const char *name; + guint64 address; + char *iter = line; + guint8 type; + + address = g_ascii_strtoull (iter, &iter, 16); + + if G_UNLIKELY ((address == 0 && errno == EINVAL) || + (address == G_MAXUINT64 && errno == ERANGE)) + goto failure; + + if G_UNLIKELY (iter[0] != ' ') + goto failure; + + /* Swallow space */ + iter++; + if (iter >= endptr) + goto failure; + + /* Get type 'ABDRTVWabdrtw' */ + type = iter[0]; + + /* Move past type and space */ + iter++; + iter++; + if (iter >= endptr) + goto failure; + + /* Name starts here */ + name = iter; + + /* Walk ahead to first space or \0 */ + while (iter < endptr && !g_ascii_isspace (*iter)) + iter++; + if (iter > endptr) + goto failure; + + /* Make @name usable as C string */ + *iter = 0; + + /* Sometimes we get duplicates in kallsyms right after one another. + * Rather than try to deduplicate those all after they're in the + * array just detect the simple case and skip them now. + */ + if (address != last_address) + sysprof_kallsyms_symbolizer_add (self, address, type, name); + + last_address = address; + + failure: + g_free (line); + } + + /* We cannot rely on sorting of kallsyms up-front from Linux in all + * cases so we must sort the resulting array now. + */ + g_array_sort (self->kallsyms, sort_by_address); + + /* Store a "best guess" at an lower/upper bound for the max address so that + * we can avoid searching for anything unreasonably past the end of the last + * kernel symbol. + */ + if (self->kallsyms->len > 0) + { + const KernelSymbol *head = &g_array_index (self->kallsyms, KernelSymbol, 0); + const KernelSymbol *tail = &g_array_index (self->kallsyms, KernelSymbol, self->kallsyms->len-1); + + self->low = head->address; + self->high = tail->address + LAST_SYMBOL_LEN; + } + + g_task_return_boolean (task, TRUE); +} + +static void +sysprof_kallsyms_symbolizer_prepare_async (SysprofSymbolizer *symbolizer, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SysprofKallsymsSymbolizer *self = (SysprofKallsymsSymbolizer *)symbolizer; + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(GInputStream) input = NULL; + g_autoptr(GTask) task = NULL; + GInputStream *base_stream; + + g_assert (SYSPROF_IS_KALLSYMS_SYMBOLIZER (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_kallsyms_symbolizer_prepare_async); + + base_stream = self->stream; + + if (base_stream == NULL) + { + if (!(file = sysprof_document_lookup_file (document, "/proc/kallsyms"))) + { + g_task_return_new_error (task, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "No kallsyms found to decode"); + return; + } + + base_stream = input = sysprof_document_file_read (file); + } + + g_assert (base_stream != NULL); + g_assert (G_IS_INPUT_STREAM (base_stream)); + + g_task_set_task_data (task, + g_data_input_stream_new (base_stream), + g_object_unref); + + g_task_run_in_thread (task, sysprof_kallsyms_symbolizer_prepare_worker); +} + +static gboolean +sysprof_kallsyms_symbolizer_prepare_finish (SysprofSymbolizer *symbolizer, + GAsyncResult *result, + GError **error) +{ + g_assert (SYSPROF_IS_KALLSYMS_SYMBOLIZER (symbolizer)); + g_assert (G_IS_TASK (result)); + g_assert (g_task_is_valid (result, symbolizer)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static SysprofSymbol * +sysprof_kallsyms_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofKallsymsSymbolizer *self = (SysprofKallsymsSymbolizer *)symbolizer; + const KernelSymbol *symbols; + guint n_symbols; + guint left; + guint right; + guint mid; + + if (context != SYSPROF_ADDRESS_CONTEXT_KERNEL) + return NULL; + + if (address < self->low || address >= self->high) + goto failure; + + symbols = &g_array_index (self->kallsyms, KernelSymbol, 0); + n_symbols = self->kallsyms->len; + + left = 0; + right = n_symbols; + mid = n_symbols / 2; + + while (left <= right) + { + const KernelSymbol *ksym = &symbols[mid]; + const KernelSymbol *next = &symbols[mid+1]; + + if (address >= ksym->address && + (address < next->address || next->address == 0)) + return _sysprof_symbol_new (g_ref_string_acquire (ksym->name), + NULL, + g_ref_string_acquire (linux_string), + ksym->address, + next->address ? next->address : ksym->address + LAST_SYMBOL_LEN, + SYSPROF_SYMBOL_KIND_KERNEL); + + if (address < ksym->address) + right = mid; + else + left = mid + 1; + + mid = left + ((right-left) / 2); + } + +failure: + { + SysprofSymbol *ret; + char name[64]; + + g_snprintf (name, sizeof name, "In Kernel+0x%"G_GINT64_MODIFIER"x", address); + ret = _sysprof_symbol_new (sysprof_strings_get (strings, name), + NULL, + g_ref_string_acquire (linux_string), + address, + address + 1, + SYSPROF_SYMBOL_KIND_KERNEL); + ret->is_fallback = TRUE; + + return ret; + } +} + +static void +sysprof_kallsyms_symbolizer_finalize (GObject *object) +{ + SysprofKallsymsSymbolizer *self = (SysprofKallsymsSymbolizer *)object; + + g_clear_object (&self->stream); + g_clear_pointer (&self->kallsyms, g_array_unref); + + G_OBJECT_CLASS (sysprof_kallsyms_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_kallsyms_symbolizer_class_init (SysprofKallsymsSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->finalize = sysprof_kallsyms_symbolizer_finalize; + + symbolizer_class->prepare_async = sysprof_kallsyms_symbolizer_prepare_async; + symbolizer_class->prepare_finish = sysprof_kallsyms_symbolizer_prepare_finish; + symbolizer_class->symbolize = sysprof_kallsyms_symbolizer_symbolize; + + kallsym_strings = sysprof_strings_new (); + linux_string = g_ref_string_new_intern ("Linux"); +} + +static void +sysprof_kallsyms_symbolizer_init (SysprofKallsymsSymbolizer *self) +{ + self->kallsyms = g_array_new (TRUE, FALSE, sizeof (KernelSymbol)); + g_array_set_clear_func (self->kallsyms, kernel_symbol_clear); +} + +SysprofSymbolizer * +sysprof_kallsyms_symbolizer_new (void) +{ + return g_object_new (SYSPROF_TYPE_KALLSYMS_SYMBOLIZER, NULL); +} + +/** + * sysprof_kallsyms_symbolizer_new_for_symbols: + * @symbols: (transfer full): a #GInputStream + * + * Creates a symbolizer using the contents of @symbols as the contents + * of `/proc/kallsyms` for decoding. This is useful if you need to unwind + * symbols from a machine that is different than where the capture was + * recorded and have not embedded `/proc/kallsyms.gz` within the capture + * file for use by #SysprofKallsymsSymbolizer. + * + * Returns: (transfer full): a #SysprofKallsymsSymbolizer + */ +SysprofSymbolizer * +sysprof_kallsyms_symbolizer_new_for_symbols (GInputStream *symbols) +{ + SysprofKallsymsSymbolizer *self; + + g_return_val_if_fail (G_IS_INPUT_STREAM (symbols), NULL); + + self = g_object_new (SYSPROF_TYPE_KALLSYMS_SYMBOLIZER, NULL); + self->stream = symbols; + + return SYSPROF_SYMBOLIZER (self); +} diff --git a/src/libsysprof/sysprof-kallsyms-symbolizer.h b/src/libsysprof/sysprof-kallsyms-symbolizer.h new file mode 100644 index 00000000..6246b2db --- /dev/null +++ b/src/libsysprof/sysprof-kallsyms-symbolizer.h @@ -0,0 +1,46 @@ +/* sysprof-kallsyms-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_KALLSYMS_SYMBOLIZER (sysprof_kallsyms_symbolizer_get_type()) +#define SYSPROF_IS_KALLSYMS_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_KALLSYMS_SYMBOLIZER) +#define SYSPROF_KALLSYMS_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_KALLSYMS_SYMBOLIZER, SysprofKallsymsSymbolizer) +#define SYSPROF_KALLSYMS_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_KALLSYMS_SYMBOLIZER, SysprofKallsymsSymbolizerClass) + +typedef struct _SysprofKallsymsSymbolizer SysprofKallsymsSymbolizer; +typedef struct _SysprofKallsymsSymbolizerClass SysprofKallsymsSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_kallsyms_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_kallsyms_symbolizer_new (void); +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_kallsyms_symbolizer_new_for_symbols (GInputStream *symbols); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofKallsymsSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-kallsyms.c b/src/libsysprof/sysprof-kallsyms.c deleted file mode 100644 index 4545fba0..00000000 --- a/src/libsysprof/sysprof-kallsyms.c +++ /dev/null @@ -1,168 +0,0 @@ -/* sysprof-kallsyms.c - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-kallsyms" - -#include "config.h" - -#include -#include -#include - -#include "sysprof-kallsyms.h" - -struct _SysprofKallsyms -{ - gchar *buf; - gsize buflen; - gchar *endptr; - gchar *iter; -}; - -void -sysprof_kallsyms_free (SysprofKallsyms *self) -{ - if (self != NULL) - { - g_clear_pointer (&self->buf, g_free); - g_slice_free (SysprofKallsyms, self); - } -} - -SysprofKallsyms * -sysprof_kallsyms_new_take (gchar *data) -{ - g_autoptr(SysprofKallsyms) self = NULL; - - self = g_slice_new0 (SysprofKallsyms); - self->buf = g_steal_pointer (&data); - self->buflen = strlen (self->buf); - self->endptr = self->buf + self->buflen; - self->iter = self->buf; - - return g_steal_pointer (&self); -} - -SysprofKallsyms * -sysprof_kallsyms_new (const gchar *path) -{ - g_autoptr(SysprofKallsyms) self = NULL; - - if (path == NULL) - path = "/proc/kallsyms"; - - self = g_slice_new0 (SysprofKallsyms); - - if (!g_file_get_contents (path, &self->buf, &self->buflen, NULL)) - return NULL; - - self->iter = self->buf; - self->endptr = self->buf + self->buflen; - - return g_steal_pointer (&self); -} - -gboolean -sysprof_kallsyms_next (SysprofKallsyms *self, - const gchar **name, - guint64 *address, - guint8 *type) -{ - guint64 addr; - char *tok; - char *pptr; - - g_return_val_if_fail (self != NULL, FALSE); - g_return_val_if_fail (self->buf != NULL, FALSE); - g_return_val_if_fail (self->buflen > 0, FALSE); - g_return_val_if_fail (self->iter != NULL, FALSE); - g_return_val_if_fail (self->endptr != NULL, FALSE); - -try_next: - - if (self->iter >= self->endptr) - return FALSE; - - tok = strtok_r (self->iter, " \t\n", &self->iter); - if (!tok || *tok == 0) - return FALSE; - - if (*tok == '[') - { - tok = strtok_r (self->iter, " \t\n", &self->iter); - if (!tok || *tok == 0) - return FALSE; - } - - /* We'll keep going if we fail to parse, (null) usually, so that we - * just skip to the next line. - */ - addr = g_ascii_strtoull (tok, &pptr, 16); - if ((pptr == tok) || (addr == G_MAXUINT64 && errno == ERANGE) || (addr == 0 && errno == EINVAL)) - addr = 0; - - *address = addr; - - if (self->iter >= self->endptr) - return FALSE; - - tok = strtok_r (self->iter, " \t\n", &self->iter); - if (!tok || *tok == 0) - return FALSE; - - switch (*tok) - { - case 'A': - case 'B': - case 'D': - case 'R': - case 'T': - case 'V': - case 'W': - case 'a': - case 'b': - case 'd': - case 'r': - case 't': - case 'w': - *type = *tok; - break; - - default: - return FALSE; - } - - if (self->iter >= self->endptr) - return FALSE; - - tok = strtok_r (self->iter, " \t\n", &self->iter); - if (!tok || *tok == 0) - return FALSE; - - if (self->iter >= self->endptr) - return FALSE; - - if (addr == 0) - goto try_next; - - *name = tok; - - return TRUE; -} diff --git a/src/libsysprof/sysprof-kallsyms.h b/src/libsysprof/sysprof-kallsyms.h deleted file mode 100644 index c50303ea..00000000 --- a/src/libsysprof/sysprof-kallsyms.h +++ /dev/null @@ -1,39 +0,0 @@ -/* sysprof-kallsyms.h - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef struct _SysprofKallsyms SysprofKallsyms; - -SysprofKallsyms *sysprof_kallsyms_new (const gchar *path); -SysprofKallsyms *sysprof_kallsyms_new_take (gchar *data); -gboolean sysprof_kallsyms_next (SysprofKallsyms *self, - const gchar **name, - guint64 *address, - guint8 *type); -void sysprof_kallsyms_free (SysprofKallsyms *self); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofKallsyms, sysprof_kallsyms_free) - -G_END_DECLS diff --git a/src/libsysprof/sysprof-kernel-symbol-resolver.c b/src/libsysprof/sysprof-kernel-symbol-resolver.c deleted file mode 100644 index 6e754297..00000000 --- a/src/libsysprof/sysprof-kernel-symbol-resolver.c +++ /dev/null @@ -1,156 +0,0 @@ -/* sysprof-kernel-symbol-resolver.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-kernel-symbol-resolver" - -#include "config.h" - -#include - -#include "sysprof-kallsyms.h" -#include "sysprof-kernel-symbol.h" -#include "sysprof-kernel-symbol-resolver.h" -#include "sysprof-private.h" - -#include "sysprof-platform.h" - -struct _SysprofKernelSymbolResolver -{ - GObject parent_instance; - SysprofKernelSymbols *symbols; -}; - -static GQuark linux_quark; - -static gchar * -sysprof_kernel_symbol_resolver_resolve_with_context (SysprofSymbolResolver *resolver, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag) -{ - SysprofKernelSymbolResolver *self = (SysprofKernelSymbolResolver *)resolver; - const SysprofKernelSymbol *sym; - - g_assert (SYSPROF_IS_SYMBOL_RESOLVER (self)); - g_assert (tag != NULL); - - if (context != SYSPROF_ADDRESS_CONTEXT_KERNEL) - return NULL; - - if (self->symbols == NULL) - return NULL; - - if ((sym = _sysprof_kernel_symbols_lookup (self->symbols, address))) - { - *tag = linux_quark; - return g_strdup (sym->name); - } - - return NULL; -} - -static void -sysprof_kernel_symbol_resolver_load (SysprofSymbolResolver *resolver, - SysprofCaptureReader *reader) -{ - static const guint8 zero[] = {0}; - SysprofKernelSymbolResolver *self = (SysprofKernelSymbolResolver *)resolver; - g_autoptr(GByteArray) bytes = NULL; - g_autoptr(SysprofKallsyms) kallsyms = NULL; - guint8 buf[4096]; - gint data_fd; - - g_assert (SYSPROF_IS_KERNEL_SYMBOL_RESOLVER (self)); - g_assert (reader != NULL); - - /* If there is an embedded __symbols__ file, then we won't do anything - * because we want to use the symbols from the peer. - */ - if (sysprof_capture_reader_find_file (reader, "__symbols__")) - return; - - sysprof_capture_reader_reset (reader); - - if (-1 == (data_fd = sysprof_memfd_create ("[sysprof-kallsyms]")) || - !sysprof_capture_reader_read_file_fd (reader, "/proc/kallsyms", data_fd)) - { - if (data_fd != -1) - close (data_fd); - self->symbols = _sysprof_kernel_symbols_get_shared (); - return; - } - - bytes = g_byte_array_new (); - lseek (data_fd, 0, SEEK_SET); - - for (;;) - { - gssize len = read (data_fd, buf, sizeof buf); - - if (len <= 0) - break; - - g_byte_array_append (bytes, buf, len); - } - - g_byte_array_append (bytes, zero, 1); - - if (bytes->len > 1) - { - kallsyms = sysprof_kallsyms_new_take ((gchar *)g_byte_array_free (g_steal_pointer (&bytes), FALSE)); - self->symbols = _sysprof_kernel_symbols_new_from_kallsyms (kallsyms); - } - else - { - self->symbols = _sysprof_kernel_symbols_get_shared (); - } -} - -static void -symbol_resolver_iface_init (SysprofSymbolResolverInterface *iface) -{ - iface->load = sysprof_kernel_symbol_resolver_load; - iface->resolve_with_context = sysprof_kernel_symbol_resolver_resolve_with_context; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofKernelSymbolResolver, - sysprof_kernel_symbol_resolver, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SYMBOL_RESOLVER, - symbol_resolver_iface_init)) - -static void -sysprof_kernel_symbol_resolver_class_init (SysprofKernelSymbolResolverClass *klass) -{ - linux_quark = g_quark_from_static_string ("Kernel"); -} - -static void -sysprof_kernel_symbol_resolver_init (SysprofKernelSymbolResolver *skernel) -{ -} - -SysprofSymbolResolver * -sysprof_kernel_symbol_resolver_new (void) -{ - return g_object_new (SYSPROF_TYPE_KERNEL_SYMBOL_RESOLVER, NULL); -} diff --git a/src/libsysprof/sysprof-kernel-symbol-resolver.h b/src/libsysprof/sysprof-kernel-symbol-resolver.h deleted file mode 100644 index 563e26f0..00000000 --- a/src/libsysprof/sysprof-kernel-symbol-resolver.h +++ /dev/null @@ -1,38 +0,0 @@ -/* sysprof-kernel-symbol-resolver.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-symbol-resolver.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_KERNEL_SYMBOL_RESOLVER (sysprof_kernel_symbol_resolver_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofKernelSymbolResolver, sysprof_kernel_symbol_resolver, SYSPROF, KERNEL_SYMBOL_RESOLVER, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSymbolResolver *sysprof_kernel_symbol_resolver_new (void); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-kernel-symbol.c b/src/libsysprof/sysprof-kernel-symbol.c deleted file mode 100644 index a474cdd9..00000000 --- a/src/libsysprof/sysprof-kernel-symbol.c +++ /dev/null @@ -1,252 +0,0 @@ -/* sysprof-kernel-symbol.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-kernel-symbol" - -#include "config.h" - -#include -#include - -#include "sysprof-helpers.h" -#include "sysprof-kallsyms.h" -#include "sysprof-kernel-symbol.h" -#include "sysprof-private.h" - -static G_LOCK_DEFINE (kernel_lock); -static GStringChunk *kernel_symbol_strs; -static GHashTable *kernel_symbols_skip_hash; -static const gchar *kernel_symbols_skip[] = { - /* IRQ stack */ - "common_interrupt", - "apic_timer_interrupt", - "smp_apic_timer_interrupt", - "hrtimer_interrupt", - "__run_hrtimer", - "perf_swevent_hrtimer", - "perf_event_overflow", - "__perf_event_overflow", - "perf_prepare_sample", - "perf_callchain", - "perf_swcounter_hrtimer", - "perf_counter_overflow", - "__perf_counter_overflow", - "perf_counter_output", - - /* NMI stack */ - "nmi_stack_correct", - "do_nmi", - "notify_die", - "atomic_notifier_call_chain", - "notifier_call_chain", - "perf_event_nmi_handler", - "perf_counter_nmi_handler", - "intel_pmu_handle_irq", - "perf_event_overflow", - "perf_counter_overflow", - "__perf_event_overflow", - "perf_prepare_sample", - "perf_callchain", -}; - -static inline gboolean -type_is_ignored (guint8 type) -{ - /* Only allow symbols in the text (code) section */ - return (type != 't' && type != 'T'); -} - -static gint -sysprof_kernel_symbol_compare (gconstpointer a, - gconstpointer b) -{ - const SysprofKernelSymbol *syma = a; - const SysprofKernelSymbol *symb = b; - - if (syma->address > symb->address) - return 1; - else if (syma->address == symb->address) - return 0; - else - return -1; -} - -static void -do_shared_init (void) -{ - static gsize once; - - if (g_once_init_enter (&once)) - { - g_autoptr(GHashTable) skip = NULL; - - kernel_symbol_strs = g_string_chunk_new (4096 * 4); - - skip = g_hash_table_new (g_str_hash, g_str_equal); - for (guint i = 0; i < G_N_ELEMENTS (kernel_symbols_skip); i++) - g_hash_table_insert (skip, (gchar *)kernel_symbols_skip[i], NULL); - kernel_symbols_skip_hash = g_steal_pointer (&skip); - - g_once_init_leave (&once, TRUE); - } -} - -SysprofKernelSymbols * -_sysprof_kernel_symbols_new_from_kallsyms (SysprofKallsyms *kallsyms) -{ - static const SysprofKernelSymbol empty = {0}; - SysprofKernelSymbols *self; - const gchar *name; - guint64 addr; - guint8 type; - - do_shared_init (); - - g_return_val_if_fail (kallsyms != NULL, NULL); - - self = g_array_new (FALSE, FALSE, sizeof (SysprofKernelSymbol)); - - G_LOCK (kernel_lock); - - while (sysprof_kallsyms_next (kallsyms, &name, &addr, &type)) - { - if (!type_is_ignored (type)) - { - SysprofKernelSymbol sym; - - sym.address = addr; - sym.name = g_string_chunk_insert_const (kernel_symbol_strs, name); - - g_array_append_val (self, sym); - } - } - - g_array_sort (self, sysprof_kernel_symbol_compare); - - /* Always add a trailing node */ - g_array_append_val (self, empty); - - G_UNLOCK (kernel_lock); - - return g_steal_pointer (&self); -} - -SysprofKernelSymbols * -_sysprof_kernel_symbols_get_shared (void) -{ - static SysprofKernelSymbols *shared; - static SysprofKernelSymbols empty[] = { 0 }; - - if (shared == NULL) - { -#ifdef __linux__ - SysprofHelpers *helpers = sysprof_helpers_get_default (); - g_autofree gchar *contents = NULL; - - if (sysprof_helpers_get_proc_file (helpers, "/proc/kallsyms", NULL, &contents, NULL)) - { - g_autoptr(SysprofKallsyms) kallsyms = sysprof_kallsyms_new_take (g_steal_pointer (&contents)); - shared = _sysprof_kernel_symbols_new_from_kallsyms (kallsyms); - } -#endif - - if (shared == NULL) - shared = empty; - } - - return shared; -} - -static const SysprofKernelSymbol * -sysprof_kernel_symbol_lookup (SysprofKernelSymbol *symbols, - SysprofCaptureAddress address, - guint first, - guint last) -{ - if (symbols == NULL) - return NULL; - - if (address >= symbols [last].address) - { - return &symbols [last]; - } - else if (last - first < 3) - { - while (last >= first) - { - if (address >= symbols[last].address) - return &symbols [last]; - - last--; - } - - return NULL; - } - else - { - int mid = (first + last) / 2; - - if (symbols [mid].address > address) - return sysprof_kernel_symbol_lookup (symbols, address, first, mid); - else - return sysprof_kernel_symbol_lookup (symbols, address, mid, last); - } -} - -/* - * sysprof_kernel_symbols_lookup: - * @self: the symbol data to lookup - * @address: the address of the instruction pointer - * - * Locates the kernel symbol that contains @address. - * - * Returns: (transfer none): An #SysprofKernelSymbol or %NULL. - */ -const SysprofKernelSymbol * -_sysprof_kernel_symbols_lookup (const SysprofKernelSymbols *self, - SysprofCaptureAddress address) -{ - const SysprofKernelSymbol *first; - const SysprofKernelSymbol *ret; - - g_assert (self != NULL); - - if (self->len < 2) - return NULL; - - /* Short circuit if this is out of range */ - first = &g_array_index (self, SysprofKernelSymbol, 0); - if (address < first->address) - return NULL; - - ret = sysprof_kernel_symbol_lookup ((SysprofKernelSymbol *)(gpointer)self->data, - address, - 0, - /* 1 for right-most, 1 for empty node */ - self->len - 2); - - /* We resolve all symbols, including ignored symbols so that we - * don't give back the wrong function juxtapose an ignored func. - */ - if (ret != NULL && g_hash_table_contains (kernel_symbols_skip_hash, ret->name)) - return NULL; - - return ret; -} diff --git a/src/libsysprof/sysprof-line-reader.c b/src/libsysprof/sysprof-line-reader.c deleted file mode 100644 index 0f10b55e..00000000 --- a/src/libsysprof/sysprof-line-reader.c +++ /dev/null @@ -1,122 +0,0 @@ -/* sysprof-line-reader.c - * - * Copyright 2015-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "sysprof-line-reader.h" - -struct _SysprofLineReader -{ - const gchar *contents; - gsize length; - gsize pos; -}; - -void -sysprof_line_reader_free (SysprofLineReader *self) -{ - g_slice_free (SysprofLineReader, self); -} - -/** - * sysprof_line_reader_new: - * @contents: The buffer to read lines from - * @length: the length of @buffer in bytes - * - * Creates a new #SysprofLineReader for the contents provided. @contents are not - * copied and therefore it is a programming error to free contents before - * freeing the #SysprofLineReader structure. - * - * Use sysprof_line_reader_next() to read through the lines of the buffer. - * - * Returns: (transfer full): A new #SysprofLineReader that should be freed with - * sysprof_line_reader_free() when no longer in use. - */ -SysprofLineReader * -sysprof_line_reader_new (const gchar *contents, - gssize length) -{ - SysprofLineReader *self = g_slice_new (SysprofLineReader); - - if (contents == NULL) - { - contents = ""; - length = 0; - } - else if (length < 0) - { - length = strlen (contents); - } - - self->contents = contents; - self->length = length; - self->pos = 0; - - return self; -} - -/** - * sysprof_line_reader_next: - * @self: the #SysprofLineReader - * @length: a location for the length of the line in bytes - * - * Moves forward to the beginning of the next line in the buffer. No changes to - * the buffer are made, and the result is a pointer within the string passed as - * @contents in sysprof_line_reader_init(). Since the line most likely will not be - * terminated with a NULL byte, you must provide @length to determine the - * length of the line. - * - * Using "line[length]" will place you on the \n that was found for the line. - * However, to perform this safely, you need to know that your string was - * either \0 terminated to begin with, or that your buffer provides enough space - * to guarantee you can dereference past the last "textual content" of the - * buffer. - * - * Returns: (nullable) (transfer none): The beginning of the line within the buffer - */ -const gchar * -sysprof_line_reader_next (SysprofLineReader *self, - gsize *length) -{ - const gchar *ret; - const gchar *endptr; - - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (length != NULL, NULL); - - if ((self->contents == NULL) || (self->pos >= self->length)) - { - *length = 0; - return NULL; - } - - ret = &self->contents [self->pos]; - - endptr = memchr (ret, '\n', self->length - self->pos); - if (G_UNLIKELY (endptr == NULL)) - endptr = &self->contents [self->length]; - - *length = (endptr - ret); - self->pos += *length + 1; - - return ret; -} diff --git a/src/libsysprof/sysprof-line-reader.h b/src/libsysprof/sysprof-line-reader.h deleted file mode 100644 index 0bb6b303..00000000 --- a/src/libsysprof/sysprof-line-reader.h +++ /dev/null @@ -1,40 +0,0 @@ -/* sysprof-line-reader.h - * - * Copyright 2015-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef struct _SysprofLineReader SysprofLineReader; - -G_GNUC_INTERNAL -SysprofLineReader *sysprof_line_reader_new (const gchar *contents, - gssize length); -G_GNUC_INTERNAL -void sysprof_line_reader_free (SysprofLineReader *self); -G_GNUC_INTERNAL -const gchar *sysprof_line_reader_next (SysprofLineReader *self, - gsize *length); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofLineReader, sysprof_line_reader_free) - -G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-procs-visualizer.h b/src/libsysprof/sysprof-linux-instrument-private.h similarity index 67% rename from src/libsysprof-ui/sysprof-procs-visualizer.h rename to src/libsysprof/sysprof-linux-instrument-private.h index c7679754..ec6c94be 100644 --- a/src/libsysprof-ui/sysprof-procs-visualizer.h +++ b/src/libsysprof/sysprof-linux-instrument-private.h @@ -1,6 +1,6 @@ -/* sysprof-procs-visualizer.h +/* sysprof-linux-instrument-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,14 @@ #pragma once -#include "sysprof-visualizer.h" +#include "sysprof-instrument-private.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_PROCS_VISUALIZER (sysprof_procs_visualizer_get_type()) +#define SYSPROF_TYPE_LINUX_INSTRUMENT (sysprof_linux_instrument_get_type()) -G_DECLARE_FINAL_TYPE (SysprofProcsVisualizer, sysprof_procs_visualizer, SYSPROF, PROCS_VISUALIZER, SysprofVisualizer) +G_DECLARE_FINAL_TYPE (SysprofLinuxInstrument, sysprof_linux_instrument, SYSPROF, LINUX_INSTRUMENT, SysprofInstrument) -SysprofVisualizer *sysprof_procs_visualizer_new (void); +SysprofInstrument *_sysprof_linux_instrument_new (void); G_END_DECLS diff --git a/src/libsysprof/sysprof-linux-instrument.c b/src/libsysprof/sysprof-linux-instrument.c new file mode 100644 index 00000000..f9266c53 --- /dev/null +++ b/src/libsysprof/sysprof-linux-instrument.c @@ -0,0 +1,440 @@ +/* sysprof-linux-instrument.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-linux-instrument-private.h" +#include "sysprof-maps-parser-private.h" +#include "sysprof-podman-private.h" +#include "sysprof-recording-private.h" + +#include "line-reader-private.h" + +struct _SysprofLinuxInstrument +{ + SysprofInstrument parent_instance; +}; + +enum { + PROP_0, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofLinuxInstrument, sysprof_linux_instrument, SYSPROF_TYPE_INSTRUMENT) + +static char ** +sysprof_linux_instrument_list_required_policy (SysprofInstrument *instrument) +{ + static const char *policy[] = {"org.gnome.sysprof3.profile", NULL}; + + return g_strdupv ((char **)policy); +} + +static void +add_mmaps (SysprofRecording *recording, + GPid pid, + const char *mapsstr, + gboolean ignore_inode, + gint64 at_time) +{ + SysprofCaptureWriter *writer; + SysprofMapsParser parser; + guint64 begin, end, offset, inode; + char *file; + + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (mapsstr != NULL); + g_assert (pid > 0); + + writer = _sysprof_recording_writer (recording); + + sysprof_maps_parser_init (&parser, mapsstr, -1); + while (sysprof_maps_parser_next (&parser, &begin, &end, &offset, &inode, &file)) + { + if (ignore_inode) + inode = 0; + + sysprof_capture_writer_add_map (writer, at_time, -1, pid, + begin, end, offset, inode, + file); + g_free (file); + } +} + +static DexFuture * +populate_overlays (SysprofRecording *recording, + SysprofPodman *podman, + int pid, + const char *cgroup, + gint64 at_time) +{ + static GRegex *flatpak_regex; + static GRegex *podman_regex; + + g_autoptr(GMatchInfo) flatpak_match = NULL; + g_autoptr(GMatchInfo) podman_match = NULL; + SysprofCaptureWriter *writer; + + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (cgroup != NULL); + + if (strcmp (cgroup, "") == 0) + return dex_future_new_for_boolean (TRUE); + + writer = _sysprof_recording_writer (recording); + + /* This function tries to discover the podman container that contains the + * process identified on the host as @pid. We can only do anything with this + * if the pids are in containers that the running user controls (so that we + * can actually access the overlays). + * + * This stuff, and I want to emphasize, is a giant hack. Just like containers + * are on Linux. But if we are really careful, we can make this work for the + * one particular use case I care about, which is podman/toolbox on Fedora + * Workstation/Silverblue. + * + * -- Christian + */ + if G_UNLIKELY (podman_regex == NULL) + { + podman_regex = g_regex_new ("libpod-([a-z0-9]{64})\\.scope", G_REGEX_OPTIMIZE, 0, NULL); + g_assert (podman_regex != NULL); + } + + if (flatpak_regex == NULL) + { + flatpak_regex = g_regex_new ("app-flatpak-([a-zA-Z_\\-\\.]+)-[0-9]+\\.scope", G_REGEX_OPTIMIZE, 0, NULL); + g_assert (flatpak_regex != NULL); + } + + if (g_regex_match (podman_regex, cgroup, 0, &podman_match)) + { + g_autofree char *word = g_match_info_fetch (podman_match, 1); + g_autofree char *path = g_strdup_printf ("/proc/%d/root/run/.containerenv", pid); + g_auto(GStrv) layers = NULL; + + if ((layers = sysprof_podman_get_layers (podman, word))) + { + for (guint i = 0; layers[i]; i++) + sysprof_capture_writer_add_overlay (writer, at_time, -1, pid, + i, layers[i], "/"); + } + + return _sysprof_recording_add_file (recording, path, FALSE); + } + else if (g_regex_match (flatpak_regex, cgroup, 0, &flatpak_match)) + { + g_autofree char *path = g_strdup_printf ("/proc/%d/root/.flatpak-info", pid); + + return _sysprof_recording_add_file (recording, path, FALSE); + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +add_process_info (SysprofRecording *recording, + GVariant *process_info, + gint64 at_time) +{ + g_autoptr(SysprofPodman) podman = NULL; + g_autoptr(GPtrArray) futures = NULL; + SysprofCaptureWriter *writer; + GVariantIter iter; + GVariant *pidinfo; + + g_assert (process_info != NULL); + g_assert (g_variant_is_of_type (process_info, G_VARIANT_TYPE ("aa{sv}"))); + + writer = _sysprof_recording_writer (recording); + podman = sysprof_podman_snapshot_current_user (); + futures = g_ptr_array_new_with_free_func (dex_unref); + + /* Loop through all the PIDs the server notified us about */ + g_variant_iter_init (&iter, process_info); + while (g_variant_iter_loop (&iter, "@a{sv}", &pidinfo)) + { + g_autofree char *mount_path = NULL; + GVariantDict dict; + const char *cmdline; + const char *comm; + const char *mountinfo; + const char *maps; + const char *cgroup; + gboolean ignore_inode; + gint32 pid; + + g_variant_dict_init (&dict, pidinfo); + + if (!g_variant_dict_lookup (&dict, "pid", "i", &pid)) + goto skip; + + if (!g_variant_dict_lookup (&dict, "cmdline", "&s", &cmdline)) + cmdline = ""; + + if (!g_variant_dict_lookup (&dict, "comm", "&s", &comm)) + comm = ""; + + if (!g_variant_dict_lookup (&dict, "mountinfo", "&s", &mountinfo)) + mountinfo = ""; + + if (!g_variant_dict_lookup (&dict, "maps", "&s", &maps)) + maps = ""; + + if (!g_variant_dict_lookup (&dict, "cgroup", "&s", &cgroup)) + cgroup = ""; + + /* Notify the capture that a process was spawned */ + sysprof_capture_writer_add_process (writer, at_time, -1, pid, + *cmdline ? cmdline : comm); + + /* Give the capture access to the mountinfo of that process to aid + * in resolving symbols later on. + */ + mount_path = g_strdup_printf ("/proc/%u/mountinfo", pid); + _sysprof_recording_add_file_data (recording, mount_path, mountinfo, -1, TRUE); + + /* Ignore inodes from podman/toolbox because they appear to always be + * wrong. We'll have to rely on CRC/build-id instead. + */ + ignore_inode = strstr (cgroup, "/libpod-") != NULL; + add_mmaps (recording, pid, maps, ignore_inode, at_time); + + /* We might have overlays that need to be applied to the process + * which can be rather combursome for old-style Podman using + * FUSE overlayfs. + */ + g_ptr_array_add (futures, populate_overlays (recording, podman, pid, cgroup, at_time)); + + skip: + g_variant_dict_clear (&dict); + } + + if (futures->len > 0) + return dex_future_allv ((DexFuture **)futures->pdata, futures->len); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_linux_instrument_prepare_fiber (gpointer user_data) +{ + SysprofRecording *recording = user_data; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_RECORDING (recording)); + + /* First get some basic information about the system into the capture. We can + * get the contents for all of these concurrently. + */ + if (!dex_await (dex_future_all (_sysprof_recording_add_file (recording, "/proc/cpuinfo", TRUE), + _sysprof_recording_add_file (recording, "/proc/mounts", TRUE), + NULL), + &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_linux_instrument_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + return dex_scheduler_spawn (NULL, 0, + sysprof_linux_instrument_prepare_fiber, + g_object_ref (recording), + g_object_unref); +} + +static void +add_process_output_as_file_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(DexPromise) promise = user_data; + g_autoptr(GError) error = NULL; + g_autofree char *stdout_buf = NULL; + + g_assert (G_IS_SUBPROCESS (object)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (DEX_IS_PROMISE (promise)); + + if (g_subprocess_communicate_utf8_finish (G_SUBPROCESS (object), result, &stdout_buf, NULL, &error)) + dex_promise_resolve_string (promise, g_steal_pointer (&stdout_buf)); + else + dex_promise_reject (promise, g_steal_pointer (&error)); +} + +static void +add_process_output_as_file (SysprofRecording *recording, + const char *command_line, + const char *filename, + gboolean compress) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GSubprocess) subprocess = NULL; + g_autoptr(DexPromise) promise = NULL; + g_autoptr(GError) error = NULL; + g_auto(GStrv) argv = NULL; + g_autofree char *string = NULL; + int argc; + + g_assert (SYSPROF_IS_RECORDING (recording)); + + if (!g_shell_parse_argv (command_line, &argc, &argv, &error)) + goto error; + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_SILENCE); + if (!(subprocess = g_subprocess_launcher_spawnv (launcher, (const char * const *)argv, &error))) + goto error; + + promise = dex_promise_new (); + g_subprocess_communicate_utf8_async (subprocess, + NULL, + dex_promise_get_cancellable (promise), + add_process_output_as_file_cb, + dex_ref (promise)); + + if (!(string = dex_await_string (dex_ref (promise), &error))) + goto error; + + _sysprof_recording_add_file_data (recording, filename, string, -1, compress); + + return; + +error: + _sysprof_recording_diagnostic (recording, + "Linux", + "Failed to get output for '%s': %s", + command_line, error->message); +} + +static DexFuture * +sysprof_linux_instrument_record_fiber (gpointer user_data) +{ + SysprofRecording *recording = user_data; + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) process_info_reply = NULL; + g_autoptr(GVariant) process_info = NULL; + g_autoptr(GError) error = NULL; + gint64 at_time; + + g_assert (SYSPROF_IS_RECORDING (recording)); + + /* Try to get some info into our capture for decoding */ + add_process_output_as_file (recording, "eglinfo", "eglinfo", TRUE); + add_process_output_as_file (recording, "glxinfo", "glxinfo", TRUE); + add_process_output_as_file (recording, "lsusb -v", "lsusb", TRUE); + + /* We need access to the bus to call various sysprofd API directly */ + if (!(bus = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* We also want to get a bunch of info on user processes so that we can add + * records about them to the recording. + */ + at_time = SYSPROF_CAPTURE_CURRENT_TIME; + if (!(process_info_reply = dex_await_variant (dex_dbus_connection_call (bus, + "org.gnome.Sysprof3", + "/org/gnome/Sysprof3", + "org.gnome.Sysprof3.Service", + "GetProcessInfo", + g_variant_new ("(s)", "pid,maps,mountinfo,cmdline,comm,cgroup"), + G_VARIANT_TYPE ("(aa{sv})"), + G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, + G_MAXINT), + &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* Add process records for each of the processes discovered */ + process_info = g_variant_get_child_value (process_info_reply, 0); + dex_await (add_process_info (recording, process_info, at_time), NULL); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_linux_instrument_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + return dex_scheduler_spawn (NULL, 0, + sysprof_linux_instrument_record_fiber, + g_object_ref (recording), + g_object_unref); +} + +static DexFuture * +sysprof_linux_instrument_process_started (SysprofInstrument *instrument, + SysprofRecording *recording, + int pid) +{ + g_autofree char *mountinfo_path = NULL; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + mountinfo_path = g_strdup_printf ("/proc/%u/mountinfo", pid); + + /* TODO: If we get the process cgroup and keep our saved podman + * state around, we could poopulate overlays for new processes. + */ + + return _sysprof_recording_add_file (recording, mountinfo_path, TRUE); +} + +static void +sysprof_linux_instrument_finalize (GObject *object) +{ + G_OBJECT_CLASS (sysprof_linux_instrument_parent_class)->finalize (object); +} + +static void +sysprof_linux_instrument_class_init (SysprofLinuxInstrumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->finalize = sysprof_linux_instrument_finalize; + + instrument_class->list_required_policy = sysprof_linux_instrument_list_required_policy; + instrument_class->prepare = sysprof_linux_instrument_prepare; + instrument_class->record = sysprof_linux_instrument_record; + instrument_class->process_started = sysprof_linux_instrument_process_started; +} + +static void +sysprof_linux_instrument_init (SysprofLinuxInstrument *self) +{ +} + +SysprofInstrument * +_sysprof_linux_instrument_new (void) +{ + return g_object_new (SYSPROF_TYPE_LINUX_INSTRUMENT, NULL); +} diff --git a/src/libsysprof/sysprof-local-profiler.c b/src/libsysprof/sysprof-local-profiler.c deleted file mode 100644 index 59b4aba2..00000000 --- a/src/libsysprof/sysprof-local-profiler.c +++ /dev/null @@ -1,1217 +0,0 @@ -/* sysprof-local-profiler.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-local-profiler" - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "sysprof-helpers.h" -#include "sysprof-local-profiler.h" -#include "sysprof-platform.h" - -#include "sysprof-capture-autocleanups.h" -#include "sysprof-control-source.h" -#include "sysprof-gjs-source.h" -#include "sysprof-hostinfo-source.h" -#ifdef __linux__ -# include "sysprof-perf-source.h" -#endif -#include "sysprof-proc-source.h" -#include "sysprof-proxy-source.h" - -#define CSTRV(s) ((const gchar * const *)s) - -typedef struct -{ - SysprofCaptureWriter *writer; - - /* All sources added */ - GPtrArray *sources; - - /* Array of GError failures */ - GPtrArray *failures; - - /* Sources currently starting */ - GPtrArray *starting; - - /* Sources currently stopping */ - GPtrArray *stopping; - - /* Sources that have failed or finished */ - GPtrArray *finished_or_failed; - - /* Pids to notify children about before prepare */ - GArray *pids; - - /* Timer for simple time tracking */ - GTimer *timer; - guint timer_notify_source; - - /* Arguments and environment variables for spawning */ - gchar **spawn_argv; - gchar **spawn_env; - gchar *spawn_cwd; - - /* State flags */ - guint is_running : 1; - guint is_stopping : 1; - guint is_starting : 1; - - /* - * If we should spawn argv when starting up. This allows UI to set - * spawn argv/env but enable disable with a toggle. - */ - guint spawn : 1; - - /* If we should inherit the environment when spawning */ - guint spawn_inherit_environ : 1; - - /* If we should inherit stdin from our process */ - guint inherit_stdin : 1; - - /* - * If we should profile the entire system. Setting this results in pids - * being ignored. This is primarily useful for UI to toggle on/off the - * feature of per-process vs whole-system. - */ - guint whole_system : 1; - - /* - * If we got a stop request after calling start() but before we have had - * a chance to settle, then we need to stop immediately after starting. - * We do this to avoid a more complex state machine (for now). - */ - guint stop_after_starting : 1; -} SysprofLocalProfilerPrivate; - -static void profiler_iface_init (SysprofProfilerInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofLocalProfiler, sysprof_local_profiler, G_TYPE_OBJECT, 0, - G_ADD_PRIVATE (SysprofLocalProfiler) - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_PROFILER, profiler_iface_init)) - -enum { - PROP_0, - PROP_INHERIT_STDIN, - N_PROPS, - - PROP_ELAPSED, - PROP_IS_MUTABLE, - PROP_IS_RUNNING, - PROP_SPAWN, - PROP_SPAWN_ARGV, - PROP_SPAWN_CWD, - PROP_SPAWN_ENV, - PROP_SPAWN_INHERIT_ENVIRON, - PROP_WHOLE_SYSTEM, -}; - -enum { - SUBPROCESS_SPAWNED, - SUBPROCESS_FINISHED, - N_SINGALS, -}; - -static GParamSpec *properties [N_PROPS]; -static guint signals [N_SINGALS]; - -static inline gint -_g_ptr_array_find (GPtrArray *ar, - gpointer item) -{ - guint i; - - for (i = 0; i < ar->len; i++) - { - if (item == g_ptr_array_index (ar, i)) - return i; - } - - return -1; -} - -static inline gboolean -_g_ptr_array_contains (GPtrArray *ar, - gpointer item) -{ - return (-1 != _g_ptr_array_find (ar, item)); -} - -static void -sysprof_local_profiler_clear_timer (SysprofLocalProfiler *self) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - - g_clear_pointer (&priv->timer, g_timer_destroy); - - if (priv->timer_notify_source != 0) - { - g_source_remove (priv->timer_notify_source); - priv->timer_notify_source = 0; - } -} - -static void -sysprof_local_profiler_real_stopped (SysprofProfiler *profiler) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - - sysprof_local_profiler_clear_timer (self); -} - -static gboolean -sysprof_local_profiler_notify_elapsed_cb (gpointer data) -{ - SysprofLocalProfiler *self = data; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - - g_object_notify (G_OBJECT (self), "elapsed"); - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_local_profiler_finish_stopping (SysprofLocalProfiler *self) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - g_autoptr(SysprofCaptureReader) reader = NULL; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - g_assert (priv->is_starting == FALSE); - g_assert (priv->is_stopping == TRUE); - g_assert (priv->stopping->len == 0); - - reader = sysprof_capture_writer_create_reader (priv->writer); - g_assert (reader != NULL); - - for (guint i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - sysprof_capture_reader_reset (reader); - sysprof_source_supplement (source, reader); - } - - if (priv->failures->len > 0) - { - const GError *error = g_ptr_array_index (priv->failures, 0); - - sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), error); - } - - priv->is_running = FALSE; - priv->is_stopping = FALSE; - - sysprof_profiler_emit_stopped (SYSPROF_PROFILER (self)); - - g_object_notify (G_OBJECT (self), "is-mutable"); - g_object_notify (G_OBJECT (self), "is-running"); -} - -static void -sysprof_local_profiler_stop (SysprofProfiler *profiler) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - guint i; - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - - if (priv->is_starting) - { - priv->stop_after_starting = TRUE; - return; - } - - if (priv->is_stopping || !priv->is_running) - return; - - priv->is_stopping = TRUE; - - /* - * First we add everything to the stopping list, so that we can - * be notified of when they have completed. If everything stopped - * synchronously, the stopping list will be empty after calling - * sysprof_source_stop() for every source. Otherwise, we need to delay - * stopping for a little bit. - */ - - for (i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - if (!_g_ptr_array_contains (priv->finished_or_failed, source)) - g_ptr_array_add (priv->stopping, g_object_ref (source)); - } - - for (i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - sysprof_source_stop (source); - } - - if (priv->is_stopping && priv->stopping->len == 0) - sysprof_local_profiler_finish_stopping (self); -} - - -static void -sysprof_local_profiler_dispose (GObject *object) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)object; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - if (priv->is_running || priv->is_starting) - { - sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); - return; - } - - sysprof_local_profiler_clear_timer (self); - - G_OBJECT_CLASS (sysprof_local_profiler_parent_class)->dispose (object); -} - -static void -sysprof_local_profiler_finalize (GObject *object) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)object; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_clear_pointer (&priv->writer, sysprof_capture_writer_unref); - g_clear_pointer (&priv->sources, g_ptr_array_unref); - g_clear_pointer (&priv->starting, g_ptr_array_unref); - g_clear_pointer (&priv->stopping, g_ptr_array_unref); - g_clear_pointer (&priv->failures, g_ptr_array_unref); - g_clear_pointer (&priv->finished_or_failed, g_ptr_array_unref); - g_clear_pointer (&priv->pids, g_array_unref); - - G_OBJECT_CLASS (sysprof_local_profiler_parent_class)->finalize (object); -} - -static void -sysprof_local_profiler_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofLocalProfiler *self = SYSPROF_LOCAL_PROFILER (object); - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - switch (prop_id) - { - case PROP_INHERIT_STDIN: - g_value_set_boolean (value, priv->inherit_stdin); - break; - - case PROP_ELAPSED: - g_value_set_double (value, priv->timer ? g_timer_elapsed (priv->timer, NULL) : 0.0); - break; - - case PROP_IS_MUTABLE: - g_value_set_boolean (value, !(priv->is_starting || priv->is_stopping || priv->is_running)); - break; - - case PROP_IS_RUNNING: - g_value_set_boolean (value, priv->is_running); - break; - - case PROP_WHOLE_SYSTEM: - g_value_set_boolean (value, priv->whole_system); - break; - - case PROP_SPAWN: - g_value_set_boolean (value, priv->spawn); - break; - - case PROP_SPAWN_INHERIT_ENVIRON: - g_value_set_boolean (value, priv->spawn_inherit_environ); - break; - - case PROP_SPAWN_ARGV: - g_value_set_boxed (value, priv->spawn_argv); - break; - - case PROP_SPAWN_CWD: - g_value_set_string (value, priv->spawn_cwd); - break; - - case PROP_SPAWN_ENV: - g_value_set_boxed (value, priv->spawn_env); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_local_profiler_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofLocalProfiler *self = SYSPROF_LOCAL_PROFILER (object); - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - switch (prop_id) - { - case PROP_INHERIT_STDIN: - sysprof_local_profiler_set_inherit_stdin (self, g_value_get_boolean (value)); - break; - - case PROP_WHOLE_SYSTEM: - priv->whole_system = g_value_get_boolean (value); - break; - - case PROP_SPAWN: - priv->spawn = g_value_get_boolean (value); - break; - - case PROP_SPAWN_INHERIT_ENVIRON: - priv->spawn_inherit_environ = g_value_get_boolean (value); - break; - - case PROP_SPAWN_ARGV: - g_strfreev (priv->spawn_argv); - priv->spawn_argv = g_value_dup_boxed (value); - break; - - case PROP_SPAWN_CWD: - g_free (priv->spawn_cwd); - priv->spawn_cwd = g_value_dup_string (value); - break; - - case PROP_SPAWN_ENV: - g_strfreev (priv->spawn_env); - priv->spawn_env = g_value_dup_boxed (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_local_profiler_class_init (SysprofLocalProfilerClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->dispose = sysprof_local_profiler_dispose; - object_class->finalize = sysprof_local_profiler_finalize; - object_class->get_property = sysprof_local_profiler_get_property; - object_class->set_property = sysprof_local_profiler_set_property; - - /** - * SysprofLocalProfiler::subprocess-spawned: - * @self: a #SysprofLocalProfiler - * @subprocess: a #GSubprocess - * - * This signal is emitted when #SysprofLocalProfiler spawns a process. - * - * Since: 3.46 - */ - signals [SUBPROCESS_SPAWNED] = - g_signal_new ("subprocess-spawned", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - NULL, - G_TYPE_NONE, 1, G_TYPE_SUBPROCESS); - - /** - * SysprofLocalProfiler::subprocess-finished: - * @self: a #SysprofLocalProfiler - * @subprocess: a #GSubprocess - * - * This signal is emitted when #SysprofLocalProfiler has determined that - * the subprocess either exited or was terminated by a signal. Use - * g_subprocess_get_if_exited() to determine if exited with exit code. - * - * Since: 3.46 - */ - signals [SUBPROCESS_FINISHED] = - g_signal_new ("subprocess-finished", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - NULL, - G_TYPE_NONE, 1, G_TYPE_SUBPROCESS); - - g_object_class_override_property (object_class, PROP_ELAPSED, "elapsed"); - g_object_class_override_property (object_class, PROP_IS_MUTABLE, "is-mutable"); - g_object_class_override_property (object_class, PROP_IS_RUNNING, "is-running"); - g_object_class_override_property (object_class, PROP_SPAWN, "spawn"); - g_object_class_override_property (object_class, PROP_SPAWN_ARGV, "spawn-argv"); - g_object_class_override_property (object_class, PROP_SPAWN_CWD, "spawn-cwd"); - g_object_class_override_property (object_class, PROP_SPAWN_ENV, "spawn-env"); - g_object_class_override_property (object_class, PROP_SPAWN_INHERIT_ENVIRON, "spawn-inherit-environ"); - g_object_class_override_property (object_class, PROP_WHOLE_SYSTEM, "whole-system"); - - /** - * SysprofLocalProfiler:inherit-stdin: - * - * Sets the profiler to inherit stdin from the calling process when spawning - * the subprocess. This has no effect if the #SysprofLocalProfiler is not - * responsible for spawning the process. - * - * Since: 3.46 - */ - properties [PROP_INHERIT_STDIN] = - g_param_spec_boolean ("inherit-stdin", - "Inherit Stdin", - "If stdin of the calling process should be inherited by the spawned process", - FALSE, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - g_type_ensure (SYSPROF_TYPE_GJS_SOURCE); -#ifdef __linux__ - g_type_ensure (SYSPROF_TYPE_HOSTINFO_SOURCE); - g_type_ensure (SYSPROF_TYPE_PROC_SOURCE); - g_type_ensure (SYSPROF_TYPE_PERF_SOURCE); -#endif - g_type_ensure (SYSPROF_TYPE_PROXY_SOURCE); -} - -static void -sysprof_local_profiler_init (SysprofLocalProfiler *self) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - priv->whole_system = TRUE; - - priv->failures = g_ptr_array_new_with_free_func ((GDestroyNotify)g_error_free); - priv->sources = g_ptr_array_new_with_free_func (g_object_unref); - priv->starting = g_ptr_array_new_with_free_func (g_object_unref); - priv->stopping = g_ptr_array_new_with_free_func (g_object_unref); - priv->finished_or_failed = g_ptr_array_new_with_free_func (g_object_unref); - priv->pids = g_array_new (FALSE, FALSE, sizeof (GPid)); -} - -SysprofProfiler * -sysprof_local_profiler_new (void) -{ - return g_object_new (SYSPROF_TYPE_LOCAL_PROFILER, NULL); -} - -static void -sysprof_local_profiler_finish_startup (SysprofLocalProfiler *self) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - guint i; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - g_assert (priv->is_starting == TRUE); - g_assert (priv->starting->len == 0); - - sysprof_local_profiler_clear_timer (self); - - priv->timer = g_timer_new (); - - /* - * Add a source to update our watchers of elapsed time. - * We use 1000 instead of add_seconds(1) so that we are - * not subject to as much drift. - */ - priv->timer_notify_source = - g_timeout_add (1000, - sysprof_local_profiler_notify_elapsed_cb, - self); - - for (i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - sysprof_source_start (source); - } - - priv->is_starting = FALSE; - - /* - * If any of the sources failed during startup, we will have a non-empty - * failures list. - */ - if (priv->failures->len > 0) - { - const GError *error = g_ptr_array_index (priv->failures, 0); - - g_object_ref (self); - sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), error); - sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); - g_object_unref (self); - return; - } - - priv->is_running = TRUE; - - g_object_notify (G_OBJECT (self), "is-mutable"); - g_object_notify (G_OBJECT (self), "is-running"); - - /* - * If all the sources are transient (in that they just generate information - * and then exit), we could be finished as soon as we complete startup. - * - * If we detect this, we stop immediately. - */ - if (priv->finished_or_failed->len == priv->sources->len || priv->stop_after_starting) - sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); -} - -static void -sysprof_local_profiler_wait_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GSubprocess *subprocess = (GSubprocess *)object; - g_autoptr(SysprofLocalProfiler) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (G_IS_SUBPROCESS (subprocess)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - - if (!g_subprocess_wait_finish (subprocess, result, &error)) - g_warning ("Wait on subprocess failed: %s", error->message); - - g_signal_emit (self, signals[SUBPROCESS_FINISHED], 0, subprocess); - - sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); -} - -static void -sysprof_local_profiler_start_after_auth (SysprofLocalProfiler *self) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - g_autofree gchar *keydata = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - gsize keylen = 0; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - - keyfile = g_key_file_new (); - - g_key_file_set_boolean (keyfile, "profiler", "whole-system", priv->whole_system); - if (priv->pids->len > 0) - g_key_file_set_integer_list (keyfile, "profiler", "pids", - (gint *)(gpointer)priv->pids->data, - priv->pids->len); - g_key_file_set_boolean (keyfile, "profiler", "spawn", priv->spawn); - g_key_file_set_boolean (keyfile, "profiler", "spawn-inherit-environ", priv->spawn_inherit_environ); - g_key_file_set_string (keyfile, "profiler", "spawn-cwd", priv->spawn_cwd ? priv->spawn_cwd : ""); - - if (priv->spawn && priv->spawn_argv && priv->spawn_argv[0]) - { - g_autoptr(GPtrArray) env = g_ptr_array_new_with_free_func (g_free); - g_autoptr(SysprofSpawnable) spawnable = sysprof_spawnable_new (); - g_autoptr(GSubprocess) subprocess = NULL; - GSubprocessFlags flags = 0; - GPid pid; - - if (priv->inherit_stdin) - flags |= G_SUBPROCESS_FLAGS_STDIN_INHERIT; - - if (priv->spawn_inherit_environ) - { - gchar **environ_ = g_get_environ (); - - for (guint i = 0; environ_[i]; i++) - g_ptr_array_add (env, environ_[i]); - g_free (environ_); - } - - if (priv->spawn_env) - { - g_key_file_set_string_list (keyfile, "profiler", "spawn-env", - (const gchar * const *)priv->spawn_env, - g_strv_length (priv->spawn_env)); - for (guint i = 0; priv->spawn_env[i]; i++) - g_ptr_array_add (env, g_strdup (priv->spawn_env[i])); - } - - g_ptr_array_add (env, NULL); - - sysprof_spawnable_set_flags (spawnable, flags); - sysprof_spawnable_set_environ (spawnable, (const gchar * const *)env->pdata); - sysprof_spawnable_append_args (spawnable, (const gchar * const *)priv->spawn_argv); - - if (priv->spawn_cwd != NULL) - sysprof_spawnable_set_cwd (spawnable, priv->spawn_cwd); - - /* Save argv before modifying */ - if (priv->spawn_argv != NULL) - g_key_file_set_string_list (keyfile, - "profiler", - "spawn-argv", - (const gchar * const *)priv->spawn_argv, - g_strv_length (priv->spawn_argv)); - - for (guint i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - sysprof_source_modify_spawn (source, spawnable); - } - - if (!(subprocess = sysprof_spawnable_spawn (spawnable, &error))) - { - g_ptr_array_add (priv->failures, g_steal_pointer (&error)); - } - else - { - const gchar *ident = g_subprocess_get_identifier (subprocess); - - pid = atoi (ident); - g_array_append_val (priv->pids, pid); - - g_subprocess_wait_async (subprocess, - NULL, - sysprof_local_profiler_wait_cb, - g_object_ref (self)); - - g_signal_emit (self, signals[SUBPROCESS_SPAWNED], 0, subprocess); - } - } - - g_key_file_set_integer (keyfile, "profiler", "n-sources", priv->sources->len); - - for (guint i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - g_autofree gchar *group = g_strdup_printf ("source-%u", i); - - g_key_file_set_string (keyfile, group, "gtype", G_OBJECT_TYPE_NAME (source)); - sysprof_source_serialize (source, keyfile, group); - - if (priv->whole_system == FALSE) - { - for (guint j = 0; j < priv->pids->len; j++) - { - GPid pid = g_array_index (priv->pids, GPid, j); - - sysprof_source_add_pid (source, pid); - } - } - - sysprof_source_set_writer (source, priv->writer); - sysprof_source_prepare (source); - } - - for (guint i = 0; i < priv->sources->len; i++) - { - SysprofSource *source = g_ptr_array_index (priv->sources, i); - - if (!sysprof_source_get_is_ready (source)) - g_ptr_array_add (priv->starting, g_object_ref (source)); - } - - if ((keydata = g_key_file_to_data (keyfile, &keylen, NULL))) - sysprof_capture_writer_add_metadata (priv->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - "local-profiler", - keydata, - keylen); - - if (priv->starting->len == 0) - sysprof_local_profiler_finish_startup (self); -} - -static void -sysprof_local_profiler_preroll_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofLocalProfiler) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - - /* For almost everything at this point, we need to have authorization - * to the helper daemon. So if this fails, just assume we are going to - * fail in general. It doesn't really help us to optimize for the case - * of user-space only profiling since we are rarely used for that. - */ - - if (!sysprof_helpers_authorize_finish (helpers, result, &error)) - sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), error); - else - sysprof_local_profiler_start_after_auth (self); -} - -static void -sysprof_local_profiler_start (SysprofProfiler *profiler) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - g_autoptr(SysprofControlSource) control_source = NULL; - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - g_return_if_fail (priv->is_running == FALSE); - g_return_if_fail (priv->is_stopping == FALSE); - g_return_if_fail (priv->is_starting == FALSE); - - g_clear_pointer (&priv->timer, g_timer_destroy); - g_object_notify (G_OBJECT (self), "elapsed"); - - control_source = sysprof_control_source_new (); - sysprof_profiler_add_source (SYSPROF_PROFILER (self), SYSPROF_SOURCE (control_source)); - - if (priv->writer == NULL) - { - SysprofCaptureWriter *writer; - int fd; - - if ((-1 == (fd = sysprof_memfd_create ("[sysprof]"))) || - (NULL == (writer = sysprof_capture_writer_new_from_fd (fd, 0)))) - { - const GError werror = { - G_FILE_ERROR, - g_file_error_from_errno (errno), - (gchar *)g_strerror (errno) - }; - - if (fd != -1) - close (fd); - - sysprof_profiler_emit_failed (SYSPROF_PROFILER (self), &werror); - - return; - } - - sysprof_profiler_set_writer (SYSPROF_PROFILER (self), writer); - g_clear_pointer (&writer, sysprof_capture_writer_unref); - } - - priv->is_running = TRUE; - priv->is_starting = TRUE; - - if (priv->failures->len > 0) - g_ptr_array_remove_range (priv->failures, 0, priv->failures->len); - - /* Start by prefolling our authorization so that future calls are cheap */ - sysprof_helpers_authorize_async (sysprof_helpers_get_default (), - NULL, - sysprof_local_profiler_preroll_cb, - g_object_ref (self)); -} - -static void -sysprof_local_profiler_set_writer (SysprofProfiler *profiler, - SysprofCaptureWriter *writer) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - g_return_if_fail (priv->is_running == FALSE); - g_return_if_fail (priv->is_stopping == FALSE); - g_return_if_fail (writer != NULL); - - if (priv->writer != writer) - { - g_clear_pointer (&priv->writer, sysprof_capture_writer_unref); - - if (writer != NULL) - priv->writer = sysprof_capture_writer_ref (writer); - } -} - -static void -sysprof_local_profiler_track_completed (SysprofLocalProfiler *self, - SysprofSource *source) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - gint i; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - g_assert (SYSPROF_IS_SOURCE (source)); - - if (!_g_ptr_array_contains (priv->finished_or_failed, source)) - g_ptr_array_add (priv->finished_or_failed, g_object_ref (source)); - - if (priv->is_starting) - { - i = _g_ptr_array_find (priv->starting, source); - - if (i >= 0) - { - g_ptr_array_remove_index (priv->starting, i); - if (priv->starting->len == 0) - sysprof_local_profiler_finish_startup (self); - } - } - - if (priv->is_stopping) - { - i = _g_ptr_array_find (priv->stopping, source); - - if (i >= 0) - { - g_ptr_array_remove_index_fast (priv->stopping, i); - - if ((priv->is_stopping == TRUE) && (priv->stopping->len == 0)) - sysprof_local_profiler_finish_stopping (self); - } - } - - if (!priv->is_starting) - { - if (priv->finished_or_failed->len == priv->sources->len) - sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); - } -} - -static void -sysprof_local_profiler_source_finished (SysprofLocalProfiler *self, - SysprofSource *source) -{ - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - g_assert (SYSPROF_IS_SOURCE (source)); - - sysprof_local_profiler_track_completed (self, source); -} - -static void -sysprof_local_profiler_source_ready (SysprofLocalProfiler *self, - SysprofSource *source) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - guint i; - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - g_assert (SYSPROF_IS_SOURCE (source)); - - for (i = 0; i < priv->starting->len; i++) - { - SysprofSource *ele = g_ptr_array_index (priv->starting, i); - - if (ele == source) - { - g_ptr_array_remove_index_fast (priv->starting, i); - - if ((priv->is_starting == TRUE) && (priv->starting->len == 0)) - sysprof_local_profiler_finish_startup (self); - - break; - } - } -} - -static void -sysprof_local_profiler_source_failed (SysprofLocalProfiler *self, - const GError *reason, - SysprofSource *source) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_assert (SYSPROF_IS_LOCAL_PROFILER (self)); - g_assert (reason != NULL); - g_assert (SYSPROF_IS_SOURCE (source)); - - g_warning ("%s failed: %s", - G_OBJECT_TYPE_NAME (source), - reason ? reason->message : "unknown error"); - - sysprof_local_profiler_track_completed (self, source); - - /* Failure emitted out of band */ - if (!priv->is_starting && !priv->is_stopping && !priv->is_running) - return; - - g_ptr_array_add (priv->failures, g_error_copy (reason)); - - /* Ignore during start/stop, we handle this in other places */ - if (priv->is_starting || priv->is_stopping) - return; - - if (priv->is_running) - sysprof_local_profiler_stop (SYSPROF_PROFILER (self)); -} - -static void -sysprof_local_profiler_add_source (SysprofProfiler *profiler, - SysprofSource *source) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - g_return_if_fail (SYSPROF_IS_SOURCE (source)); - g_return_if_fail (priv->is_running == FALSE); - g_return_if_fail (priv->is_starting == FALSE); - g_return_if_fail (priv->is_stopping == FALSE); - - g_signal_connect_object (source, - "failed", - G_CALLBACK (sysprof_local_profiler_source_failed), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (source, - "finished", - G_CALLBACK (sysprof_local_profiler_source_finished), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (source, - "ready", - G_CALLBACK (sysprof_local_profiler_source_ready), - self, - G_CONNECT_SWAPPED); - - - g_ptr_array_add (priv->sources, g_object_ref (source)); -} - -static void -sysprof_local_profiler_add_pid (SysprofProfiler *profiler, - GPid pid) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - g_return_if_fail (pid > -1); - g_return_if_fail (priv->is_starting == FALSE); - g_return_if_fail (priv->is_stopping == FALSE); - g_return_if_fail (priv->is_running == FALSE); - - g_array_append_val (priv->pids, pid); -} - -static void -sysprof_local_profiler_remove_pid (SysprofProfiler *profiler, - GPid pid) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - guint i; - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - g_return_if_fail (pid > -1); - g_return_if_fail (priv->is_starting == FALSE); - g_return_if_fail (priv->is_stopping == FALSE); - g_return_if_fail (priv->is_running == FALSE); - - for (i = 0; i < priv->pids->len; i++) - { - GPid ele = g_array_index (priv->pids, GPid, i); - - if (ele == pid) - { - g_array_remove_index_fast (priv->pids, i); - break; - } - } -} - -static const GPid * -sysprof_local_profiler_get_pids (SysprofProfiler *profiler, - guint *n_pids) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_LOCAL_PROFILER (self), NULL); - g_return_val_if_fail (n_pids != NULL, NULL); - - *n_pids = priv->pids->len; - - return (GPid *)(gpointer)priv->pids->data; -} - -static SysprofCaptureWriter * -sysprof_local_profiler_get_writer (SysprofProfiler *profiler) -{ - SysprofLocalProfiler *self = (SysprofLocalProfiler *)profiler; - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_LOCAL_PROFILER (self), NULL); - - return priv->writer; -} - -static void -profiler_iface_init (SysprofProfilerInterface *iface) -{ - iface->add_pid = sysprof_local_profiler_add_pid; - iface->add_source = sysprof_local_profiler_add_source; - iface->get_pids = sysprof_local_profiler_get_pids; - iface->get_writer = sysprof_local_profiler_get_writer; - iface->remove_pid = sysprof_local_profiler_remove_pid; - iface->set_writer = sysprof_local_profiler_set_writer; - iface->start = sysprof_local_profiler_start; - iface->stop = sysprof_local_profiler_stop; - iface->stopped = sysprof_local_profiler_real_stopped; -} - -static bool -find_profiler_meta_cb (const SysprofCaptureFrame *frame, - void *user_data) -{ - const SysprofCaptureMetadata *meta = (const SysprofCaptureMetadata *)frame; - GKeyFile **keyfile = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_METADATA); - g_assert (keyfile != NULL); - g_assert (*keyfile == NULL); - - if (g_strcmp0 (meta->id, "local-profiler") == 0) - { - g_autoptr(GKeyFile) kf = g_key_file_new (); - - /* Metadata is guaranteed to be \0 terminated by marshaller */ - if (g_key_file_load_from_data (kf, meta->metadata, -1, 0, NULL)) - *keyfile = g_steal_pointer (&kf); - - return *keyfile == NULL; - } - - return true; -} - -SysprofProfiler * -sysprof_local_profiler_new_replay (SysprofCaptureReader *reader) -{ - static const SysprofCaptureFrameType mtype[] = { - SYSPROF_CAPTURE_FRAME_METADATA, - }; - g_autoptr(SysprofLocalProfiler) self = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - g_autoptr(GKeyFile) keyfile = NULL; - g_autofree gchar *cwd = NULL; - g_auto(GStrv) argv = NULL; - g_auto(GStrv) env = NULL; - gboolean whole_system; - gboolean inherit; - gboolean spawn; - gint n_sources; - - g_return_val_if_fail (reader != NULL, NULL); - - self = g_object_new (SYSPROF_TYPE_LOCAL_PROFILER, NULL); - - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, sysprof_capture_condition_new_where_type_in (1, mtype)); - sysprof_capture_cursor_foreach (cursor, find_profiler_meta_cb, &keyfile); - - /* No metadata, bail */ - if (keyfile == NULL) - return NULL; - - spawn = g_key_file_get_boolean (keyfile, "profiler", "spawn", NULL); - inherit = g_key_file_get_boolean (keyfile, "profiler", "spawn-inherit-environ", NULL); - argv = g_key_file_get_string_list (keyfile, "profiler", "spawn-argv", NULL, NULL); - env = g_key_file_get_string_list (keyfile, "profiler", "spawn-env", NULL, NULL); - cwd = g_key_file_get_string (keyfile, "profiler", "spawn-cwd", NULL); - n_sources = g_key_file_get_integer (keyfile, "profiler", "n-sources", NULL); - whole_system = g_key_file_get_boolean (keyfile, "profiler", "whole-system", NULL); - - /* Ignore empty string */ - if (cwd != NULL && *cwd == 0) - g_clear_pointer (&cwd, g_free); - - sysprof_profiler_set_spawn (SYSPROF_PROFILER (self), spawn); - sysprof_profiler_set_spawn_argv (SYSPROF_PROFILER (self), CSTRV (argv)); - sysprof_profiler_set_spawn_cwd (SYSPROF_PROFILER (self), cwd); - sysprof_profiler_set_spawn_env (SYSPROF_PROFILER (self), CSTRV (env)); - sysprof_profiler_set_spawn_inherit_environ (SYSPROF_PROFILER (self), inherit); - sysprof_profiler_set_whole_system (SYSPROF_PROFILER (self), whole_system); - - for (guint i = 0; i < n_sources; i++) - { - g_autofree gchar *group = g_strdup_printf ("source-%u", i); - g_autofree gchar *type_name = NULL; - g_autoptr(SysprofSource) source = NULL; - GType gtype; - - if (!g_key_file_has_group (keyfile, group) || - !(type_name = g_key_file_get_string (keyfile, group, "gtype", NULL)) || - !(gtype = g_type_from_name (type_name)) || - !g_type_is_a (gtype, SYSPROF_TYPE_SOURCE) || - !(source = g_object_new (gtype, NULL))) - continue; - - sysprof_source_deserialize (source, keyfile, group); - sysprof_local_profiler_add_source (SYSPROF_PROFILER (self), source); - } - - return SYSPROF_PROFILER (g_steal_pointer (&self)); -} - -/** - * sysprof_local_profiler_get_inherit_stdin: - * - * Since: 3.46 - */ -gboolean -sysprof_local_profiler_get_inherit_stdin (SysprofLocalProfiler *self) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_LOCAL_PROFILER (self), FALSE); - - return priv->inherit_stdin; -} - -/** - * sysprof_local_profiler_set_inherit_stdin: - * - * Since: 3.46 - */ -void -sysprof_local_profiler_set_inherit_stdin (SysprofLocalProfiler *self, - gboolean inherit_stdin) -{ - SysprofLocalProfilerPrivate *priv = sysprof_local_profiler_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_LOCAL_PROFILER (self)); - - inherit_stdin = !!inherit_stdin; - - if (priv->inherit_stdin != inherit_stdin) - { - priv->inherit_stdin = inherit_stdin; - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INHERIT_STDIN]); - } -} diff --git a/src/libsysprof/sysprof-local-profiler.h b/src/libsysprof/sysprof-local-profiler.h deleted file mode 100644 index 469f93c8..00000000 --- a/src/libsysprof/sysprof-local-profiler.h +++ /dev/null @@ -1,51 +0,0 @@ -/* sysprof-local-profiler.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-profiler.h" -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_LOCAL_PROFILER (sysprof_local_profiler_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_DERIVABLE_TYPE (SysprofLocalProfiler, sysprof_local_profiler, SYSPROF, LOCAL_PROFILER, GObject) - -struct _SysprofLocalProfilerClass -{ - GObjectClass parent_class; - gpointer padding[8]; -}; - -SYSPROF_AVAILABLE_IN_ALL -SysprofProfiler *sysprof_local_profiler_new (void); -SYSPROF_AVAILABLE_IN_ALL -SysprofProfiler *sysprof_local_profiler_new_replay (SysprofCaptureReader *reader); -SYSPROF_AVAILABLE_IN_3_46 -void sysprof_local_profiler_set_inherit_stdin (SysprofLocalProfiler *self, - gboolean inherit_stdin); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-malloc-tracing.c b/src/libsysprof/sysprof-malloc-tracing.c new file mode 100644 index 00000000..e9bcae5c --- /dev/null +++ b/src/libsysprof/sysprof-malloc-tracing.c @@ -0,0 +1,73 @@ +/* sysprof-malloc-tracing.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-malloc-tracing.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" +#include "sysprof-spawnable.h" + +struct _SysprofMallocTracing +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofMallocTracingClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofMallocTracing, sysprof_malloc_tracing, SYSPROF_TYPE_INSTRUMENT) + +static DexFuture * +sysprof_malloc_tracing_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofSpawnable *spawnable; + + g_assert (SYSPROF_IS_MALLOC_TRACING (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if ((spawnable = _sysprof_recording_get_spawnable (recording))) + sysprof_spawnable_add_ld_preload (spawnable, + PACKAGE_LIBDIR"/libsysprof-memory-" API_VERSION_S ".so"); + + return dex_future_new_for_boolean (TRUE); +} + +static void +sysprof_malloc_tracing_class_init (SysprofMallocTracingClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->prepare = sysprof_malloc_tracing_prepare; +} + +static void +sysprof_malloc_tracing_init (SysprofMallocTracing *self) +{ +} + +SysprofInstrument * +sysprof_malloc_tracing_new (void) +{ + return g_object_new (SYSPROF_TYPE_MALLOC_TRACING, NULL); +} diff --git a/src/libsysprof/sysprof-malloc-tracing.h b/src/libsysprof/sysprof-malloc-tracing.h new file mode 100644 index 00000000..2f30e64b --- /dev/null +++ b/src/libsysprof/sysprof-malloc-tracing.h @@ -0,0 +1,42 @@ +/* sysprof-malloc-tracing.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MALLOC_TRACING (sysprof_malloc_tracing_get_type()) +#define SYSPROF_IS_MALLOC_TRACING(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_MALLOC_TRACING) +#define SYSPROF_MALLOC_TRACING(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_MALLOC_TRACING, SysprofMallocTracing) +#define SYSPROF_MALLOC_TRACING_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_MALLOC_TRACING, SysprofMallocTracingClass) + +typedef struct _SysprofMallocTracing SysprofMallocTracing; +typedef struct _SysprofMallocTracingClass SysprofMallocTracingClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_malloc_tracing_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_malloc_tracing_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMallocTracing, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-map-lookaside.c b/src/libsysprof/sysprof-map-lookaside.c deleted file mode 100644 index 719b93c2..00000000 --- a/src/libsysprof/sysprof-map-lookaside.c +++ /dev/null @@ -1,143 +0,0 @@ -/* sysprof-map-lookaside.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "sysprof-map-lookaside.h" - -static gint -sysprof_map_compare (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - const SysprofMap *map_a = a; - const SysprofMap *map_b = b; - - return sysprof_capture_address_compare (map_a->start, map_b->start); -} - -static gint -sysprof_map_compare_in_range (gconstpointer a, - gconstpointer b, - gpointer user_data) -{ - const SysprofMap *map_a = a; - const SysprofMap *map_b = b; - - /* - * map_b is the needle for the search. - * Only map_b->start is set. - */ - - if ((map_b->start >= map_a->start) && (map_b->start < map_a->end)) - return 0; - - return sysprof_capture_address_compare (map_a->start, map_b->start); -} - -static void -sysprof_map_free (gpointer data) -{ - SysprofMap *map = data; - - g_slice_free (SysprofMap, map); -} - -SysprofMapLookaside * -sysprof_map_lookaside_new (void) -{ - SysprofMapLookaside *ret; - - ret = g_slice_new0 (SysprofMapLookaside); - ret->seq = g_sequence_new (sysprof_map_free); - ret->chunk = g_string_chunk_new (4096); - ret->overlays = NULL; - - return ret; -} - -void -sysprof_map_lookaside_free (SysprofMapLookaside *self) -{ - g_sequence_free (self->seq); - g_string_chunk_free (self->chunk); - g_slice_free (SysprofMapLookaside, self); -} - -void -sysprof_map_lookaside_insert (SysprofMapLookaside *self, - const SysprofMap *map) -{ - SysprofMap *copy; - - g_assert (self != NULL); - g_assert (map != NULL); - - copy = g_slice_new (SysprofMap); - copy->start = map->start; - copy->end = map->end; - copy->offset = map->offset; - copy->inode = map->inode; - copy->filename = g_string_chunk_insert_const (self->chunk, map->filename); - - g_sequence_insert_sorted (self->seq, copy, sysprof_map_compare, NULL); -} - - -void -sysprof_map_lookaside_overlay (SysprofMapLookaside *self, - const gchar *src, - const gchar *dst) -{ - SysprofMapOverlay overlay; - - g_assert (self != NULL); - g_assert (src != NULL); - g_assert (dst != NULL); - - if (!src[0] || !dst[0]) - return; - - if (self->overlays == NULL) - self->overlays = g_array_new (FALSE, FALSE, sizeof (SysprofMapOverlay)); - - overlay.src = g_string_chunk_insert_const (self->chunk, src); - overlay.dst = g_string_chunk_insert_const (self->chunk, dst); - g_array_append_val (self->overlays, overlay); -} - -const SysprofMap * -sysprof_map_lookaside_lookup (SysprofMapLookaside *self, - SysprofCaptureAddress address) -{ - SysprofMap map = { address }; - GSequenceIter *iter; - - g_assert (self != NULL); - - iter = g_sequence_lookup (self->seq, &map, sysprof_map_compare_in_range, NULL); - - if (iter != NULL) - return g_sequence_get (iter); - - return NULL; -} diff --git a/src/libsysprof/sysprof-map-lookaside.h b/src/libsysprof/sysprof-map-lookaside.h deleted file mode 100644 index 9cbc8b9d..00000000 --- a/src/libsysprof/sysprof-map-lookaside.h +++ /dev/null @@ -1,63 +0,0 @@ -/* sysprof-map-lookaside.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-capture-types.h" - -G_BEGIN_DECLS - -typedef struct _SysprofMapOverlay -{ - const char *src; - const char *dst; -} SysprofMapOverlay; - -typedef struct _SysprofMapLookaside -{ - GSequence *seq; - GStringChunk *chunk; - GArray *overlays; -} SysprofMapLookaside; - -typedef struct -{ - SysprofCaptureAddress start; - SysprofCaptureAddress end; - off_t offset; - ino_t inode; - const gchar *filename; -} SysprofMap; - -SysprofMapLookaside *sysprof_map_lookaside_new (void); -void sysprof_map_lookaside_insert (SysprofMapLookaside *self, - const SysprofMap *map); -void sysprof_map_lookaside_overlay (SysprofMapLookaside *self, - const gchar *src, - const gchar *dst); -const SysprofMap *sysprof_map_lookaside_lookup (SysprofMapLookaside *self, - SysprofCaptureAddress address); -void sysprof_map_lookaside_free (SysprofMapLookaside *self); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMapLookaside, sysprof_map_lookaside_free) - -G_END_DECLS diff --git a/src/libsysprof/sysprof-maps-parser-private.h b/src/libsysprof/sysprof-maps-parser-private.h new file mode 100644 index 00000000..4fbb373e --- /dev/null +++ b/src/libsysprof/sysprof-maps-parser-private.h @@ -0,0 +1,44 @@ +/* sysprof-maps-parser-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "line-reader-private.h" + +G_BEGIN_DECLS + +typedef struct _SysprofMapsParser +{ + LineReader reader; +} SysprofMapsParser; + +void sysprof_maps_parser_init (SysprofMapsParser *self, + const char *str, + gssize len); +gboolean sysprof_maps_parser_next (SysprofMapsParser *self, + guint64 *out_begin_addr, + guint64 *out_end_addr, + guint64 *out_offset, + guint64 *out_inode, + char **out_filename); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-maps-parser.c b/src/libsysprof/sysprof-maps-parser.c new file mode 100644 index 00000000..200b1f86 --- /dev/null +++ b/src/libsysprof/sysprof-maps-parser.c @@ -0,0 +1,114 @@ +/* sysprof-maps-parser.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-maps-parser-private.h" + +static GRegex *address_range_regex; + +void +sysprof_maps_parser_init (SysprofMapsParser *self, + const char *str, + gssize len) +{ + line_reader_init (&self->reader, (char *)str, len); + + if (address_range_regex == NULL) + address_range_regex = g_regex_new ("^([0-9a-f]+)-([0-9a-f]+) [r\\-][w\\-][x\\-][ps\\-] ([0-9a-f]+) [0-9]{2}:[0-9]{2} ([0-9]+) +(.*)$", + G_REGEX_OPTIMIZE, + G_REGEX_MATCH_DEFAULT, + NULL); +} + +gboolean +sysprof_maps_parser_next (SysprofMapsParser *self, + guint64 *out_begin_addr, + guint64 *out_end_addr, + guint64 *out_offset, + guint64 *out_inode, + char **out_filename) +{ + const char *line; + gsize len; + + while ((line = line_reader_next (&self->reader, &len))) + { + g_autoptr(GMatchInfo) match_info = NULL; + + if (g_regex_match_full (address_range_regex, line, len, 0, 0, &match_info, NULL)) + { + g_autofree char *file = NULL; + guint64 begin_addr; + guint64 end_addr; + guint64 inode; + guint64 offset; + gboolean is_vdso; + int begin_addr_begin; + int begin_addr_end; + int end_addr_begin; + int end_addr_end; + int offset_begin; + int offset_end; + int inode_begin; + int inode_end; + int path_begin; + int path_end; + + + if (!g_match_info_fetch_pos (match_info, 1, &begin_addr_begin, &begin_addr_end) || + !g_match_info_fetch_pos (match_info, 2, &end_addr_begin, &end_addr_end) || + !g_match_info_fetch_pos (match_info, 3, &offset_begin, &offset_end) || + !g_match_info_fetch_pos (match_info, 4, &inode_begin, &inode_end) || + !g_match_info_fetch_pos (match_info, 5, &path_begin, &path_end)) + continue; + + begin_addr = g_ascii_strtoull (&line[begin_addr_begin], NULL, 16); + end_addr = g_ascii_strtoull (&line[end_addr_begin], NULL, 16); + offset = g_ascii_strtoull (&line[offset_begin], NULL, 16); + inode = g_ascii_strtoull (&line[inode_begin], NULL, 10); + + if (memcmp (" (deleted", + &line[path_end] - strlen (" (deleted"), + strlen (" (deleted")) == 0) + path_end -= strlen (" (deleted)"); + + file = g_strndup (&line[path_begin], path_end-path_begin); + + is_vdso = strcmp ("[vdso]", file) == 0; + + if (is_vdso) + inode = 0; + + if (is_vdso) + offset = 0; + + *out_begin_addr = begin_addr; + *out_end_addr = end_addr; + *out_offset = offset; + *out_inode = inode; + *out_filename = g_steal_pointer (&file); + + return TRUE; + } + } + + return FALSE; +} diff --git a/src/libsysprof/sysprof-mark-catalog-private.h b/src/libsysprof/sysprof-mark-catalog-private.h new file mode 100644 index 00000000..a32dea2f --- /dev/null +++ b/src/libsysprof/sysprof-mark-catalog-private.h @@ -0,0 +1,33 @@ +/* sysprof-mark-catalog-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-mark-catalog.h" + +G_BEGIN_DECLS + +SysprofMarkCatalog *_sysprof_mark_catalog_new (const char *group, + const char *name, + GListModel *items); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-mark-catalog.c b/src/libsysprof/sysprof-mark-catalog.c new file mode 100644 index 00000000..b615b821 --- /dev/null +++ b/src/libsysprof/sysprof-mark-catalog.c @@ -0,0 +1,165 @@ +/* sysprof-mark-catalog.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-mark-catalog-private.h" + +struct _SysprofMarkCatalog +{ + GObject parent_instance; + GListModel *items; + char *group; + char *name; +} SysprofMarkCatalogPrivate; + +enum { + PROP_0, + PROP_GROUP, + PROP_NAME, + N_PROPS +}; + +static GType +sysprof_mark_catalog_get_item_type (GListModel *model) +{ + return g_list_model_get_item_type (SYSPROF_MARK_CATALOG (model)->items); +} + +static guint +sysprof_mark_catalog_get_n_items (GListModel *model) +{ + return g_list_model_get_n_items (SYSPROF_MARK_CATALOG (model)->items); +} + +static gpointer +sysprof_mark_catalog_get_item (GListModel *model, + guint position) +{ + return g_list_model_get_item (SYSPROF_MARK_CATALOG (model)->items, position); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_n_items = sysprof_mark_catalog_get_n_items; + iface->get_item_type = sysprof_mark_catalog_get_item_type; + iface->get_item = sysprof_mark_catalog_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofMarkCatalog, sysprof_mark_catalog, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_mark_catalog_dispose (GObject *object) +{ + SysprofMarkCatalog *self = (SysprofMarkCatalog *)object; + + g_clear_pointer (&self->group, g_free); + g_clear_pointer (&self->name, g_free); + g_clear_object (&self->items); + + G_OBJECT_CLASS (sysprof_mark_catalog_parent_class)->dispose (object); +} + +static void +sysprof_mark_catalog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkCatalog *self = SYSPROF_MARK_CATALOG (object); + + switch (prop_id) + { + case PROP_GROUP: + g_value_set_string (value, sysprof_mark_catalog_get_group (self)); + break; + + case PROP_NAME: + g_value_set_string (value, sysprof_mark_catalog_get_name (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_catalog_class_init (SysprofMarkCatalogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_mark_catalog_dispose; + object_class->get_property = sysprof_mark_catalog_get_property; + + properties[PROP_GROUP] = + g_param_spec_string ("group", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_NAME] = + g_param_spec_string ("name", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_mark_catalog_init (SysprofMarkCatalog *self) +{ +} + +const char * +sysprof_mark_catalog_get_group (SysprofMarkCatalog *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_CATALOG (self), NULL); + + return self->group; +} + +const char * +sysprof_mark_catalog_get_name (SysprofMarkCatalog *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_CATALOG (self), NULL); + + return self->name; +} + +SysprofMarkCatalog * +_sysprof_mark_catalog_new (const char *group, + const char *name, + GListModel *items) +{ + SysprofMarkCatalog *self; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (G_IS_LIST_MODEL (items), NULL); + + self = g_object_new (SYSPROF_TYPE_MARK_CATALOG, NULL); + self->group = g_strdup (group); + self->name = g_strdup (name); + self->items = g_object_ref (items); + + return self; +} diff --git a/src/libsysprof/sysprof-capture-frame-object.h b/src/libsysprof/sysprof-mark-catalog.h similarity index 69% rename from src/libsysprof/sysprof-capture-frame-object.h rename to src/libsysprof/sysprof-mark-catalog.h index ee64063e..61c38a49 100644 --- a/src/libsysprof/sysprof-capture-frame-object.h +++ b/src/libsysprof/sysprof-mark-catalog.h @@ -1,4 +1,4 @@ -/* sysprof-capture-frame-object.h +/* sysprof-mark-catalog.h * * Copyright 2023 Christian Hergert * @@ -20,18 +20,20 @@ #pragma once -#include +#include #include G_BEGIN_DECLS -#define SYSPROF_TYPE_CAPTURE_FRAME_OBJECT (sysprof_capture_frame_object_get_type()) +#define SYSPROF_TYPE_MARK_CATALOG (sysprof_mark_catalog_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofCaptureFrameObject, sysprof_capture_frame_object, SYSPROF, CAPTURE_FRAME_OBJECT, GObject) +G_DECLARE_FINAL_TYPE (SysprofMarkCatalog, sysprof_mark_catalog, SYSPROF, MARK_CATALOG, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofCaptureFrame *sysprof_capture_frame_object_get_frame (SysprofCaptureFrameObject *self); +const char *sysprof_mark_catalog_get_group (SysprofMarkCatalog *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_mark_catalog_get_name (SysprofMarkCatalog *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-memory-source.c b/src/libsysprof/sysprof-memory-source.c deleted file mode 100644 index 20cc88c9..00000000 --- a/src/libsysprof/sysprof-memory-source.c +++ /dev/null @@ -1,473 +0,0 @@ -/* sysprof-memory-source.c - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-memory-source" - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "sysprof-helpers.h" -#include "sysprof-memory-source.h" - -#define BUF_SIZE 4096 - -struct _SysprofMemorySource -{ - GObject parent_instance; - - /* Capture writer to deliver samples */ - SysprofCaptureWriter *writer; - - /* 4k stat buffer for reading proc */ - gchar *stat_buf; - - /* Array of MemStat */ - GArray *mem_stats; - - /* Timeout to perform polling */ - guint timer_source; -}; - -typedef struct -{ - GPid pid; - int stat_fd; - - union { - struct { - SysprofCaptureCounterValue used; - gint64 total; - gint64 avail; - gint64 free; - } sys; - struct { - SysprofCaptureCounterValue used; - gint64 size; - gint64 resident; - gint64 shared; - gint64 text; - gint64 data; - } proc; - }; - - guint counter_ids[1]; -} MemStat; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofMemorySource, sysprof_memory_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static GHashTable *keys; - -static void -mem_stat_open (MemStat *st) -{ - SysprofHelpers *helpers = sysprof_helpers_get_default (); - g_autoptr(GError) error = NULL; - - g_assert (st != NULL); - g_assert (st->stat_fd == -1); - - if (st->pid != -1) - { - g_autofree gchar *path = g_strdup_printf ("/proc/%d/statm", st->pid); - - if (!sysprof_helpers_get_proc_fd (helpers, path, NULL, &st->stat_fd, &error)) - g_warning ("Failed to access statm for pid %d: %s", st->pid, error->message); - } - else - { - if (!sysprof_helpers_get_proc_fd (helpers, "/proc/meminfo", NULL, &st->stat_fd, &error)) - g_warning ("Failed to access /proc/statm: %s", error->message); - } -} - -static void -mem_stat_close (MemStat *st) -{ - g_assert (st != NULL); - - if (st->stat_fd != -1) - { - close (st->stat_fd); - st->stat_fd = -1; - } -} - -static void -mem_stat_parse_statm (MemStat *st, - gchar *buf) -{ - g_assert (st != NULL); - g_assert (buf != NULL); - - sscanf (buf, - "%"G_GINT64_FORMAT" " - "%"G_GINT64_FORMAT" " - "%"G_GINT64_FORMAT" " - "%"G_GINT64_FORMAT" " - "%*1c " - "%"G_GINT64_FORMAT, - &st->proc.size, - &st->proc.resident, - &st->proc.shared, - &st->proc.text, - &st->proc.data); - - st->proc.used.vdbl = st->proc.size - st->proc.shared - st->proc.text - st->proc.data; -} - -static void -mem_stat_parse_meminfo (MemStat *st, - gchar *buf) -{ - gchar *bufptr = buf; - gchar *save = NULL; - - g_assert (st != NULL); - g_assert (buf != NULL); - - for (;;) - { - goffset off; - gchar *key; - gchar *value; - gchar *unit; - gint64 v64; - gint64 *v64ptr; - - /* Get the data key name */ - if (!(key = strtok_r (bufptr, " \n\t:", &save))) - break; - - bufptr = NULL; - - /* Offset from self to save value. Stop after getting to - * last value we care about. - */ - if (!(off = GPOINTER_TO_UINT (g_hash_table_lookup (keys, key)))) - break; - - /* Get the data value */ - if (!(value = strtok_r (bufptr, " \n\t:", &save))) - break; - - /* Parse the numeric value of this column */ - v64 = g_ascii_strtoll (value, NULL, 10); - if ((v64 == G_MININT64 || v64 == G_MAXINT64) && errno == ERANGE) - break; - - /* Get the data unit */ - unit = strtok_r (bufptr, " \n\t:", &save); - - if (g_strcmp0 (unit, "kB") == 0) - v64 *= 1024; - else if (g_strcmp0 (unit, "mB") == 0) - v64 *= 1024 * 1024; - - v64ptr = (gint64 *)(gpointer)(((gchar *)st) + off); - - *v64ptr = v64; - } - - /* Create pre-compiled value for used to simplify display */ - st->sys.used.vdbl = (gdouble)st->sys.total - (gdouble)st->sys.avail; -} - -static void -mem_stat_poll (MemStat *st, - gchar *stat_buf) -{ - gssize r; - - g_assert (st != NULL); - g_assert (st->stat_fd != -1); - - /* Seek to begin of FD to reset the proc read */ - if ((r = lseek (st->stat_fd, 0, SEEK_SET)) < 0) - return; - - /* Now read the updated proc buffer */ - if ((r = read (st->stat_fd, stat_buf, BUF_SIZE)) < 0) - return; - - if (r < BUF_SIZE) - stat_buf[r] = '\0'; - stat_buf[BUF_SIZE-1] = '\0'; - - if (st->pid == -1) - mem_stat_parse_meminfo (st, stat_buf); - else - mem_stat_parse_statm (st, stat_buf); -} - -static void -mem_stat_publish (MemStat *st, - SysprofCaptureWriter *writer, - gint64 current_time) -{ - g_assert (st != NULL); - g_assert (writer != NULL); - - sysprof_capture_writer_set_counters (writer, - current_time, - -1, - st->pid, - st->counter_ids, - st->pid == -1 ? &st->sys.used : &st->proc.used, - 1); -} - -/** - * sysprof_memory_source_new: - * - * Create a new #SysprofMemorySource. - * - * Use this source to record basic memory statistics about the system - * such as Available, Free, and Total Memory. - * - * Returns: (transfer full): a newly created #SysprofMemorySource - - * Since: 3.32 - */ -SysprofSource * -sysprof_memory_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_MEMORY_SOURCE, NULL); -} - -static void -sysprof_memory_source_finalize (GObject *object) -{ - SysprofMemorySource *self = (SysprofMemorySource *)object; - - if (self->timer_source != 0) - { - g_source_remove (self->timer_source); - self->timer_source = 0; - } - - g_clear_pointer (&self->stat_buf, g_free); - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - g_clear_pointer (&self->mem_stats, g_array_unref); - - G_OBJECT_CLASS (sysprof_memory_source_parent_class)->finalize (object); -} - -static void -sysprof_memory_source_class_init (SysprofMemorySourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_memory_source_finalize; - - keys = g_hash_table_new (g_str_hash, g_str_equal); - -#define ADD_OFFSET(n,o) \ - g_hash_table_insert (keys, (gchar *)n, GUINT_TO_POINTER (o)) - ADD_OFFSET ("MemTotal", G_STRUCT_OFFSET (MemStat, sys.total)); - ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, sys.free)); - ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, sys.avail)); -#undef ADD_OFFSET -} - -static void -sysprof_memory_source_init (SysprofMemorySource *self) -{ - self->stat_buf = g_malloc (BUF_SIZE); - self->mem_stats = g_array_new (FALSE, FALSE, sizeof (MemStat)); -} - -static void -sysprof_memory_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofMemorySource *self = (SysprofMemorySource *)source; - - g_assert (SYSPROF_IS_SOURCE (self)); - g_assert (writer != NULL); - g_assert (self->writer == NULL); - - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -sysprof_memory_source_prepare (SysprofSource *source) -{ - SysprofMemorySource *self = (SysprofMemorySource *)source; - - g_assert (SYSPROF_IS_MEMORY_SOURCE (self)); - g_assert (self->writer != NULL); - - /* If no pids are registered, setup a system memory stat */ - if (self->mem_stats->len == 0) - { - MemStat st = {0}; - - st.pid = -1; - st.stat_fd = -1; - - g_array_append_val (self->mem_stats, st); - } - - /* Open FDs to all the necessary files in proc. We will re-use - * them to avoid re-opening files later. - */ - for (guint i = 0; i < self->mem_stats->len; i++) - { - MemStat *st = &g_array_index (self->mem_stats, MemStat, i); - SysprofCaptureCounter counters[5]; - guint base; - - mem_stat_open (st); - - if (st->pid == -1) - { - base = sysprof_capture_writer_request_counter (self->writer, 1); - - g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category); - g_strlcpy (counters[0].name, "Used", sizeof counters[0].name); - g_strlcpy (counters[0].description, "Memory used by system", sizeof counters[0].description); - - counters[0].id = st->counter_ids[0] = base; - counters[0].type = SYSPROF_CAPTURE_COUNTER_DOUBLE; - counters[0].value.vdbl = 0; - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - counters, - 1); - } - else - { - base = sysprof_capture_writer_request_counter (self->writer, 1); - - g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category); - g_strlcpy (counters[0].name, "Used", sizeof counters[0].name); - g_strlcpy (counters[0].description, "Memory used by process", sizeof counters[0].description); - - counters[0].id = st->counter_ids[0] = base; - counters[0].type = SYSPROF_CAPTURE_COUNTER_DOUBLE; - counters[0].value.vdbl = 0; - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - st->pid, - counters, - 1); - } - } - - sysprof_source_emit_ready (SYSPROF_SOURCE (self)); -} - -static gboolean -sysprof_memory_source_timer_cb (SysprofMemorySource *self) -{ - gint64 current_time; - - g_assert (SYSPROF_IS_MEMORY_SOURCE (self)); - g_assert (self->writer != NULL); - - current_time = sysprof_clock_get_current_time (); - - for (guint i = 0; i < self->mem_stats->len; i++) - { - MemStat *st = &g_array_index (self->mem_stats, MemStat, i); - - mem_stat_poll (st, self->stat_buf); - mem_stat_publish (st, self->writer, current_time); - } - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_memory_source_start (SysprofSource *source) -{ - SysprofMemorySource *self = (SysprofMemorySource *)source; - - g_assert (SYSPROF_IS_MEMORY_SOURCE (self)); - - /* Poll 4x/sec for memory stats */ - self->timer_source = g_timeout_add_full (G_PRIORITY_HIGH, - 1000 / 4, - (GSourceFunc)sysprof_memory_source_timer_cb, - self, - NULL); -} - -static void -sysprof_memory_source_stop (SysprofSource *source) -{ - SysprofMemorySource *self = (SysprofMemorySource *)source; - - g_assert (SYSPROF_IS_MEMORY_SOURCE (self)); - - if (self->timer_source != 0) - { - g_source_remove (self->timer_source); - self->timer_source = 0; - } - - for (guint i = 0; i < self->mem_stats->len; i++) - { - MemStat *st = &g_array_index (self->mem_stats, MemStat, i); - - mem_stat_close (st); - } - - sysprof_source_emit_finished (source); -} - -static void -sysprof_memory_source_add_pid (SysprofSource *source, - GPid pid) -{ - SysprofMemorySource *self = (SysprofMemorySource *)source; - MemStat st = {0}; - - g_assert (SYSPROF_IS_MEMORY_SOURCE (self)); - - st.pid = pid; - st.stat_fd = -1; - - g_array_append_val (self->mem_stats, st); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->set_writer = sysprof_memory_source_set_writer; - iface->prepare = sysprof_memory_source_prepare; - iface->start = sysprof_memory_source_start; - iface->stop = sysprof_memory_source_stop; - iface->add_pid = sysprof_memory_source_add_pid; -} diff --git a/src/libsysprof/sysprof-memory-usage.c b/src/libsysprof/sysprof-memory-usage.c new file mode 100644 index 00000000..a4172643 --- /dev/null +++ b/src/libsysprof/sysprof-memory-usage.c @@ -0,0 +1,305 @@ +/* sysprof-memory-usage.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "line-reader-private.h" + +#include "sysprof-memory-usage.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +struct _SysprofMemoryUsage +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofMemoryUsageClass +{ + SysprofInstrumentClass parent_class; +}; + +typedef struct _MemStat +{ + int stat_fd; + + union { + struct { + SysprofCaptureCounterValue used; + gint64 total; + gint64 avail; + gint64 free; + } sys; + }; +} MemStat; + +G_DEFINE_FINAL_TYPE (SysprofMemoryUsage, sysprof_memory_usage, SYSPROF_TYPE_INSTRUMENT) + +static GHashTable *keys; + +static gboolean +mem_stat_open (MemStat *st, + GError **error) +{ + memset (st, 0, sizeof *st); + + st->stat_fd = open ("/proc/meminfo", O_RDONLY|O_CLOEXEC); + + if (st->stat_fd == -1) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + return FALSE; + } + + return TRUE; +} + +static gboolean +mem_stat_close (MemStat *st, + GError **error) +{ + g_assert (st != NULL); + + return g_clear_fd (&st->stat_fd, error); +} + +static void +mem_stat_parse (MemStat *st, + char *buf) +{ + char *bufptr = buf; + char *save = NULL; + + g_assert (st != NULL); + g_assert (buf != NULL); + + for (;;) + { + goffset off; + char *key; + char *value; + char *unit; + gint64 v64; + gint64 *v64ptr; + + /* Get the data key name */ + if (!(key = strtok_r (bufptr, " \n\t:", &save))) + break; + + bufptr = NULL; + + /* Offset from self to save value. Stop after getting to + * last value we care about. + */ + if (!(off = GPOINTER_TO_UINT (g_hash_table_lookup (keys, key)))) + break; + + /* Get the data value */ + if (!(value = strtok_r (bufptr, " \n\t:", &save))) + break; + + /* Parse the numeric value of this column */ + v64 = g_ascii_strtoll (value, NULL, 10); + if ((v64 == G_MININT64 || v64 == G_MAXINT64) && errno == ERANGE) + break; + + /* Get the data unit */ + unit = strtok_r (bufptr, " \n\t:", &save); + + if (g_strcmp0 (unit, "kB") == 0) + v64 *= 1024; + else if (g_strcmp0 (unit, "mB") == 0) + v64 *= 1024 * 1024; + + v64ptr = (gint64 *)(gpointer)(((gchar *)st) + off); + + *v64ptr = v64; + } + + /* Create pre-compiled value for used to simplify display */ + st->sys.used.vdbl = (gdouble)st->sys.total - (gdouble)st->sys.avail; +} + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; +} Record; + +static void +record_free (gpointer data) +{ + Record *record = data; + + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_free (record); +} + +static DexFuture * +sysprof_memory_usage_record_fiber (gpointer user_data) +{ + g_autoptr(GByteArray) buf = NULL; + Record *record = user_data; + SysprofCaptureWriter *writer; + g_autoptr(GError) error = NULL; + SysprofCaptureCounter counters[1]; + MemStat st; + guint counter_id; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + buf = g_byte_array_new (); + g_byte_array_set_size (buf, 4096); + + writer = _sysprof_recording_writer (record->recording); + + if (!mem_stat_open (&st, &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + counter_id = sysprof_capture_writer_request_counter (writer, 1); + + g_strlcpy (counters[0].category, "Memory", sizeof counters[0].category); + g_strlcpy (counters[0].name, "Used", sizeof counters[0].name); + g_strlcpy (counters[0].description, "Memory used by system", sizeof counters[0].description); + + counters[0].id = counter_id; + counters[0].type = SYSPROF_CAPTURE_COUNTER_DOUBLE; + counters[0].value.vdbl = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + counters, + 1); + + for (;;) + { + g_autoptr(DexFuture) read_future = dex_aio_read (NULL, st.stat_fd, buf->data, buf->len-1, 0); + gssize n_read; + + dex_await (dex_future_first (dex_ref (read_future), + dex_ref (record->cancellable), + NULL), + NULL); + + if (dex_future_get_status (read_future) != DEX_FUTURE_STATUS_RESOLVED) + break; + + n_read = dex_await_int64 (dex_ref (read_future), NULL); + if (n_read <= 0) + break; + + if (n_read > 0) + { + buf->data[n_read] = 0; + + mem_stat_parse (&st, (char *)buf->data); + + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + &counter_id, + &st.sys.used, + 1); + } + + dex_await (dex_future_first (dex_ref (record->cancellable), + dex_timeout_new_usec (G_USEC_PER_SEC / 2), + NULL), + NULL); + + if (dex_future_get_status (record->cancellable) == DEX_FUTURE_STATUS_REJECTED) + break; + } + + if (!mem_stat_close (&st, &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_memory_usage_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_MEMORY_USAGE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_memory_usage_record_fiber, + record, + record_free); +} + +static void +sysprof_memory_usage_class_init (SysprofMemoryUsageClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_memory_usage_record; + + keys = g_hash_table_new (g_str_hash, g_str_equal); + +#define ADD_OFFSET(n,o) \ + g_hash_table_insert (keys, (gchar *)n, GUINT_TO_POINTER (o)) + ADD_OFFSET ("MemTotal", G_STRUCT_OFFSET (MemStat, sys.total)); + ADD_OFFSET ("MemFree", G_STRUCT_OFFSET (MemStat, sys.free)); + ADD_OFFSET ("MemAvailable", G_STRUCT_OFFSET (MemStat, sys.avail)); +#undef ADD_OFFSET +} + +static void +sysprof_memory_usage_init (SysprofMemoryUsage *self) +{ +} + +SysprofInstrument * +sysprof_memory_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_MEMORY_USAGE, NULL); +} diff --git a/src/libsysprof/sysprof-memory-usage.h b/src/libsysprof/sysprof-memory-usage.h new file mode 100644 index 00000000..4b63f868 --- /dev/null +++ b/src/libsysprof/sysprof-memory-usage.h @@ -0,0 +1,42 @@ +/* sysprof-memory-usage.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MEMORY_USAGE (sysprof_memory_usage_get_type()) +#define SYSPROF_IS_MEMORY_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_MEMORY_USAGE) +#define SYSPROF_MEMORY_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_MEMORY_USAGE, SysprofMemoryUsage) +#define SYSPROF_MEMORY_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_MEMORY_USAGE, SysprofMemoryUsageClass) + +typedef struct _SysprofMemoryUsage SysprofMemoryUsage; +typedef struct _SysprofMemoryUsageClass SysprofMemoryUsageClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_memory_usage_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_memory_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMemoryUsage, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-memprof-profile.c b/src/libsysprof/sysprof-memprof-profile.c deleted file mode 100644 index 33da6bc5..00000000 --- a/src/libsysprof/sysprof-memprof-profile.c +++ /dev/null @@ -1,1173 +0,0 @@ -/* sysprof-memprof-profile.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-memprof-profile" - -#include "config.h" - -#include - -#include "sysprof-capture-autocleanups.h" -#include "sysprof-capture-symbol-resolver.h" -#include "sysprof-elf-symbol-resolver.h" -#include "sysprof-kernel-symbol-resolver.h" -#include "sysprof-memprof-profile.h" -#include "sysprof-symbol-resolver.h" - -#include "rax.h" -#include "../stackstash.h" - -typedef struct -{ - gint pid; - gint tid; - gint64 time; - SysprofCaptureAddress addr; - gint64 size; - guint64 frame_num; -} Alloc; - -typedef struct -{ - volatile gint ref_count; - SysprofSelection *selection; - SysprofCaptureReader *reader; - GPtrArray *resolvers; - GStringChunk *symbols; - GHashTable *tags; - GHashTable *cmdlines; - StackStash *stash; - StackStash *building; - rax *rax; - GArray *resolved; - SysprofMemprofMode mode; - SysprofMemprofStats stats; -} Generate; - -struct _SysprofMemprofProfile -{ - GObject parent_instance; - SysprofSelection *selection; - SysprofCaptureReader *reader; - Generate *g; - SysprofMemprofMode mode; -}; - -static void profile_iface_init (SysprofProfileInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofMemprofProfile, sysprof_memprof_profile, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_PROFILE, profile_iface_init)) - -enum { - PROP_0, - PROP_SELECTION, - N_PROPS -}; - -static GParamSpec *properties[N_PROPS]; - -static void -generate_finalize (Generate *g) -{ - g_clear_pointer (&g->reader, sysprof_capture_reader_unref); - g_clear_pointer (&g->rax, raxFree); - g_clear_pointer (&g->stash, stack_stash_unref); - g_clear_pointer (&g->building, stack_stash_unref); - g_clear_pointer (&g->resolvers, g_ptr_array_unref); - g_clear_pointer (&g->symbols, g_string_chunk_free); - g_clear_pointer (&g->tags, g_hash_table_unref); - g_clear_pointer (&g->resolved, g_array_unref); - g_clear_pointer (&g->cmdlines, g_hash_table_unref); - g_clear_object (&g->selection); - g_slice_free (Generate, g); -} - -static Generate * -generate_ref (Generate *g) -{ - g_return_val_if_fail (g != NULL, NULL); - g_return_val_if_fail (g->ref_count > 0, NULL); - - g_atomic_int_inc (&g->ref_count); - - return g; -} - -static void -generate_unref (Generate *g) -{ - g_return_if_fail (g != NULL); - g_return_if_fail (g->ref_count > 0); - - if (g_atomic_int_dec_and_test (&g->ref_count)) - generate_finalize (g); -} - -static void -sysprof_memprof_profile_finalize (GObject *object) -{ - SysprofMemprofProfile *self = (SysprofMemprofProfile *)object; - - g_clear_pointer (&self->g, generate_unref); - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - g_clear_object (&self->selection); - - G_OBJECT_CLASS (sysprof_memprof_profile_parent_class)->finalize (object); -} - -static void -sysprof_memprof_profile_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofMemprofProfile *self = SYSPROF_MEMPROF_PROFILE (object); - - switch (prop_id) - { - case PROP_SELECTION: - g_value_set_object (value, self->selection); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_memprof_profile_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofMemprofProfile *self = SYSPROF_MEMPROF_PROFILE (object); - - switch (prop_id) - { - case PROP_SELECTION: - self->selection = g_value_dup_object (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_memprof_profile_class_init (SysprofMemprofProfileClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_memprof_profile_finalize; - object_class->get_property = sysprof_memprof_profile_get_property; - object_class->set_property = sysprof_memprof_profile_set_property; - - properties [PROP_SELECTION] = - g_param_spec_object ("selection", - "Selection", - "The selection for filtering the callgraph", - SYSPROF_TYPE_SELECTION, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_memprof_profile_init (SysprofMemprofProfile *self) -{ - self->mode = SYSPROF_MEMPROF_MODE_ALL_ALLOCS; -} - -SysprofMemprofMode -sysprof_memprof_profile_get_mode (SysprofMemprofProfile *self) -{ - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), 0); - - return self->mode; -} - -void -sysprof_memprof_profile_set_mode (SysprofMemprofProfile *self, - SysprofMemprofMode mode) -{ - g_return_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self)); - - self->mode = mode; -} - -SysprofProfile * -sysprof_memprof_profile_new (void) -{ - return g_object_new (SYSPROF_TYPE_MEMPROF_PROFILE, NULL); -} - -static void -sysprof_memprof_profile_set_reader (SysprofProfile *profile, - SysprofCaptureReader *reader) -{ - SysprofMemprofProfile *self = (SysprofMemprofProfile *)profile; - - g_assert (SYSPROF_IS_MEMPROF_PROFILE (self)); - g_assert (reader != NULL); - - if (reader != self->reader) - { - g_clear_pointer (&self->reader, sysprof_capture_reader_unref); - self->reader = sysprof_capture_reader_ref (reader); - } -} - -static SysprofCaptureCursor * -create_cursor (SysprofCaptureReader *reader) -{ - static SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_ALLOCATION, - SYSPROF_CAPTURE_FRAME_PROCESS, - }; - SysprofCaptureCursor *cursor; - SysprofCaptureCondition *cond; - - cond = sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types); - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, cond); - - return cursor; -} - -static bool -all_allocs_foreach_cb (const SysprofCaptureFrame *frame, - void *user_data) -{ - Generate *g = user_data; - - g_assert (frame != NULL); - g_assert (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION || - frame->type == SYSPROF_CAPTURE_FRAME_PROCESS); - - if G_UNLIKELY (frame->type == SYSPROF_CAPTURE_FRAME_PROCESS) - { - const SysprofCaptureProcess *pr = (const SysprofCaptureProcess *)frame; - - if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (frame->pid))) - { - g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline); - g_hash_table_insert (g->cmdlines, - GINT_TO_POINTER (frame->pid), - (gchar *)g_string_chunk_insert_const (g->symbols, cmdline)); - } - - return true; - } - - /* Short-circuit if we don't care about this frame */ - if (!sysprof_selection_contains (g->selection, frame->time)) - return true; - - if (frame->type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev = (const SysprofCaptureAllocation *)frame; - - /* Handle memory allocations */ - if (ev->alloc_size > 0) - { - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - const gchar *cmdline; - StackNode *node; - guint len = 5; - - node = stack_stash_add_trace (g->building, ev->addrs, ev->n_addrs, ev->alloc_size); - - for (const StackNode *iter = node; iter != NULL; iter = iter->parent) - len++; - - if (G_UNLIKELY (g->resolved->len < len)) - g_array_set_size (g->resolved, len); - - len = 0; - - for (const StackNode *iter = node; iter != NULL; iter = iter->parent) - { - SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; - SysprofAddress address = iter->data; - const gchar *symbol = NULL; - - if (sysprof_address_is_context_switch (address, &context)) - { - if (last_context) - symbol = sysprof_address_context_to_string (last_context); - else - symbol = NULL; - - last_context = context; - } - else - { - for (guint i = 0; i < g->resolvers->len; i++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i); - GQuark tag = 0; - gchar *str; - - str = sysprof_symbol_resolver_resolve_with_context (resolver, - frame->time, - frame->pid, - last_context, - address, - &tag); - - if (str != NULL) - { - symbol = g_string_chunk_insert_const (g->symbols, str); - g_free (str); - - if (tag != 0 && !g_hash_table_contains (g->tags, symbol)) - g_hash_table_insert (g->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag)); - - break; - } - } - } - - if (symbol != NULL) - g_array_index (g->resolved, SysprofAddress, len++) = POINTER_TO_U64 (symbol); - } - - if ((cmdline = g_hash_table_lookup (g->cmdlines, GINT_TO_POINTER (frame->pid)))) - g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 (cmdline); - - g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]"); - - stack_stash_add_trace (g->stash, - (gpointer)g->resolved->data, - len, - ev->alloc_size); - } - } - - return true; -} - -static gint -compare_frame_num_reverse (gconstpointer a, - gconstpointer b) -{ - const Alloc *aptr = a; - const Alloc *bptr = b; - - if (aptr->frame_num < bptr->frame_num) - return 1; - else if (aptr->frame_num > bptr->frame_num) - return -1; - else - return 0; -} - - -static gint -compare_alloc (gconstpointer a, - gconstpointer b) -{ - const Alloc *aptr = a; - const Alloc *bptr = b; - - if (aptr->pid < bptr->pid) - return -1; - else if (aptr->pid > bptr->pid) - return 1; - - if (aptr->tid < bptr->tid) - return -1; - else if (aptr->tid > bptr->tid) - return 1; - - if (aptr->time < bptr->time) - return -1; - else if (aptr->time > bptr->time) - return 1; - - if (aptr->addr < bptr->addr) - return -1; - else if (aptr->addr > bptr->addr) - return 1; - else - return 0; -} - -static gint -compare_alloc_pid_addr_time (gconstpointer a, - gconstpointer b) -{ - const Alloc *aptr = a; - const Alloc *bptr = b; - - if (aptr->pid < bptr->pid) - return -1; - else if (aptr->pid > bptr->pid) - return 1; - - if (aptr->addr < bptr->addr) - return -1; - else if (aptr->addr > bptr->addr) - return 1; - - if (aptr->time < bptr->time) - return -1; - else if (aptr->time > bptr->time) - return 1; - else - return 0; -} - -static guint -get_bucket (gint64 size) -{ - if (size <= 32) - return 0; - if (size <= 64) - return 1; - if (size <= 128) - return 2; - if (size <= 256) - return 3; - if (size <= 512) - return 4; - if (size <= 1024) - return 5; - if (size <= 4096) - return 6; - if (size <= 4096*4) - return 7; - if (size <= 4096*8) - return 8; - if (size <= 4096*16) - return 9; - if (size <= 4096*32) - return 10; - if (size <= 4096*64) - return 11; - if (size <= 4096*256) - return 12; - return 13; -} - -static void -summary_worker (Generate *g) -{ - g_autoptr(GArray) allocs = NULL; - SysprofCaptureFrameType type; - SysprofCaptureAddress last_addr = 0; - guint last_bucket = 0; - - g_assert (g != NULL); - g_assert (g->reader != NULL); - - allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); - - sysprof_capture_reader_reset (g->reader); - - g->stats.by_size[0].bucket = 32; - g->stats.by_size[1].bucket = 64; - g->stats.by_size[2].bucket = 128; - g->stats.by_size[3].bucket = 256; - g->stats.by_size[4].bucket = 512; - g->stats.by_size[5].bucket = 1024; - g->stats.by_size[6].bucket = 4096; - g->stats.by_size[7].bucket = 4096*4; - g->stats.by_size[8].bucket = 4096*8; - g->stats.by_size[9].bucket = 4096*16; - g->stats.by_size[10].bucket = 4096*32; - g->stats.by_size[11].bucket = 4096*64; - g->stats.by_size[12].bucket = 4096*256; - g->stats.by_size[13].bucket = 4096*256; - - while (sysprof_capture_reader_peek_type (g->reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev; - Alloc a; - - if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) - break; - - a.pid = ev->frame.pid; - a.tid = ev->tid; - a.time = ev->frame.time; - a.addr = ev->alloc_addr; - a.size = ev->alloc_size; - a.frame_num = 0; - - g_array_append_val (allocs, a); - - if (a.size > 0) - g->stats.n_allocs++; - } - else - { - if (!sysprof_capture_reader_skip (g->reader)) - break; - } - } - - g_array_sort (allocs, compare_alloc); - - for (guint i = 0; i < allocs->len; i++) - { - const Alloc *a = &g_array_index (allocs, Alloc, i); - - if (a->size <= 0) - { - if (last_addr == a->addr) - { - g->stats.temp_allocs++; - g->stats.by_size[last_bucket].temp_allocs++; - } - - g->stats.leaked_allocs--; - - last_addr = 0; - last_bucket = 0; - } - else - { - guint b = get_bucket (a->size); - - g->stats.n_allocs++; - g->stats.leaked_allocs++; - g->stats.by_size[b].n_allocs++; - g->stats.by_size[b].allocated += a->size; - - last_addr = a->addr; - last_bucket = b; - } - } -} - -static void -temp_allocs_worker (Generate *g) -{ - g_autoptr(GArray) temp_allocs = NULL; - g_autoptr(GArray) all_allocs = NULL; - StackNode *node; - SysprofCaptureFrameType type; - SysprofCaptureAddress last_addr = 0; - guint64 frame_num = 0; - - g_assert (g != NULL); - g_assert (g->reader != NULL); - - temp_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); - all_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); - - sysprof_capture_reader_reset (g->reader); - - while (sysprof_capture_reader_peek_type (g->reader, &type)) - { - if G_UNLIKELY (type == SYSPROF_CAPTURE_FRAME_PROCESS) - { - const SysprofCaptureProcess *pr; - - if (!(pr = sysprof_capture_reader_read_process (g->reader))) - break; - - if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (pr->frame.pid))) - { - g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline); - g_hash_table_insert (g->cmdlines, - GINT_TO_POINTER (pr->frame.pid), - (gchar *)g_string_chunk_insert_const (g->symbols, cmdline)); - } - } - else if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev; - Alloc a; - - if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) - break; - - frame_num++; - - /* Short-circuit if we don't care about this frame */ - if (!sysprof_selection_contains (g->selection, ev->frame.time)) - continue; - - a.pid = ev->frame.pid; - a.tid = ev->tid; - a.time = ev->frame.time; - a.addr = ev->alloc_addr; - a.size = ev->alloc_size; - a.frame_num = frame_num; - - g_array_append_val (all_allocs, a); - } - else - { - if (!sysprof_capture_reader_skip (g->reader)) - break; - } - } - - /* Ensure items are in order because threads may be writing - * data into larger buffers, which are flushed in whole by - * the event marshalling from control fds. - */ - g_array_sort (all_allocs, compare_alloc); - - for (guint i = 0; i < all_allocs->len; i++) - { - const Alloc *a = &g_array_index (all_allocs, Alloc, i); - - if (a->size <= 0) - { - if (a->addr == last_addr && last_addr) - { - const Alloc *prev = a - 1; - g_array_append_vals (temp_allocs, prev, 1); - } - - last_addr = 0; - } - else - { - last_addr = a->addr; - } - } - - if (temp_allocs->len == 0) - return; - - /* Now sort by frame number so we can walk the reader and get the stack - * for each allocation as we count frames. We can skip frames until we - * get to the matching frame_num for the next alloc. - * - * We sort in reverse so that we can just keep shortening the array as - * we match each frame to save having to keep a secondary position - * variable. - */ - g_array_sort (temp_allocs, compare_frame_num_reverse); - sysprof_capture_reader_reset (g->reader); - frame_num = 0; - while (sysprof_capture_reader_peek_type (g->reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev; - const Alloc *tail; - - if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) - break; - - frame_num++; - - tail = &g_array_index (temp_allocs, Alloc, temp_allocs->len - 1); - - if G_UNLIKELY (tail->frame_num == frame_num) - { - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - const gchar *cmdline; - guint len = 5; - - node = stack_stash_add_trace (g->building, ev->addrs, ev->n_addrs, ev->alloc_size); - - for (const StackNode *iter = node; iter != NULL; iter = iter->parent) - len++; - - if (G_UNLIKELY (g->resolved->len < len)) - g_array_set_size (g->resolved, len); - - len = 0; - - for (const StackNode *iter = node; iter != NULL; iter = iter->parent) - { - SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; - SysprofAddress address = iter->data; - const gchar *symbol = NULL; - - if (sysprof_address_is_context_switch (address, &context)) - { - if (last_context) - symbol = sysprof_address_context_to_string (last_context); - else - symbol = NULL; - - last_context = context; - } - else - { - for (guint i = 0; i < g->resolvers->len; i++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i); - GQuark tag = 0; - gchar *str; - - str = sysprof_symbol_resolver_resolve_with_context (resolver, - ev->frame.time, - ev->frame.pid, - last_context, - address, - &tag); - - if (str != NULL) - { - symbol = g_string_chunk_insert_const (g->symbols, str); - g_free (str); - - if (tag != 0 && !g_hash_table_contains (g->tags, symbol)) - g_hash_table_insert (g->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag)); - - break; - } - } - } - - if (symbol != NULL) - g_array_index (g->resolved, SysprofAddress, len++) = POINTER_TO_U64 (symbol); - } - - if ((cmdline = g_hash_table_lookup (g->cmdlines, GINT_TO_POINTER (ev->frame.pid)))) - g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 (cmdline); - - g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]"); - - stack_stash_add_trace (g->stash, - (gpointer)g->resolved->data, - len, - ev->alloc_size); - - g_array_set_size (temp_allocs, temp_allocs->len - 1); - - if (temp_allocs->len == 0) - break; - } - } - else - { - if (!sysprof_capture_reader_skip (g->reader)) - break; - } - } -} - -static void -leaked_allocs_worker (Generate *g) -{ - g_autoptr(GArray) leak_allocs = NULL; - g_autoptr(GArray) all_allocs = NULL; - StackNode *node; - SysprofCaptureFrameType type; - guint64 frame_num = 0; - - g_assert (g != NULL); - g_assert (g->reader != NULL); - - leak_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); - all_allocs = g_array_new (FALSE, FALSE, sizeof (Alloc)); - - sysprof_capture_reader_reset (g->reader); - - while (sysprof_capture_reader_peek_type (g->reader, &type)) - { - if G_UNLIKELY (type == SYSPROF_CAPTURE_FRAME_PROCESS) - { - const SysprofCaptureProcess *pr; - - if (!(pr = sysprof_capture_reader_read_process (g->reader))) - break; - - if (!g_hash_table_contains (g->cmdlines, GINT_TO_POINTER (pr->frame.pid))) - { - g_autofree gchar *cmdline = g_strdup_printf ("[%s]", pr->cmdline); - g_hash_table_insert (g->cmdlines, - GINT_TO_POINTER (pr->frame.pid), - (gchar *)g_string_chunk_insert_const (g->symbols, cmdline)); - } - } - else if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev; - Alloc a; - - if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) - break; - - frame_num++; - - /* Short-circuit if we don't care about this frame */ - if (!sysprof_selection_contains (g->selection, ev->frame.time)) - continue; - - a.pid = ev->frame.pid; - a.tid = ev->tid; - a.time = ev->frame.time; - a.addr = ev->alloc_addr; - a.size = ev->alloc_size; - a.frame_num = frame_num; - - g_array_append_val (all_allocs, a); - } - else - { - if (!sysprof_capture_reader_skip (g->reader)) - break; - } - } - - if (all_allocs->len == 0) - return; - - /* Order so we can find frees right after alloc */ - g_array_sort (all_allocs, compare_alloc_pid_addr_time); - - for (guint i = 0; i < all_allocs->len; i++) - { - const Alloc *a = &g_array_index (all_allocs, Alloc, i); - const Alloc *next; - - /* free()s are <= 0 */ - if (a->size <= 0) - continue; - - if (i + 1 == all_allocs->len) - goto leaked; - - next = &g_array_index (all_allocs, Alloc, i+1); - if (a->addr == next->addr && a->pid == next->pid && next->size <= 0) - continue; - - leaked: - g_array_append_vals (leak_allocs, a, 1); - } - - if (leak_allocs->len == 0) - return; - - /* Now sort by frame number so we can walk the reader and get the stack - * for each allocation as we count frames. We can skip frames until we - * get to the matching frame_num for the next alloc. - * - * We sort in reverse so that we can just keep shortening the array as - * we match each frame to save having to keep a secondary position - * variable. - */ - g_array_sort (leak_allocs, compare_frame_num_reverse); - sysprof_capture_reader_reset (g->reader); - frame_num = 0; - while (sysprof_capture_reader_peek_type (g->reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev; - const Alloc *tail; - - if (!(ev = sysprof_capture_reader_read_allocation (g->reader))) - break; - - frame_num++; - - tail = &g_array_index (leak_allocs, Alloc, leak_allocs->len - 1); - - if G_UNLIKELY (tail->frame_num == frame_num) - { - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - const gchar *cmdline; - guint len = 5; - - node = stack_stash_add_trace (g->building, ev->addrs, ev->n_addrs, ev->alloc_size); - - for (const StackNode *iter = node; iter != NULL; iter = iter->parent) - len++; - - if (G_UNLIKELY (g->resolved->len < len)) - g_array_set_size (g->resolved, len); - - len = 0; - - for (const StackNode *iter = node; iter != NULL; iter = iter->parent) - { - SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; - SysprofAddress address = iter->data; - const gchar *symbol = NULL; - - if (sysprof_address_is_context_switch (address, &context)) - { - if (last_context) - symbol = sysprof_address_context_to_string (last_context); - else - symbol = NULL; - - last_context = context; - } - else - { - for (guint i = 0; i < g->resolvers->len; i++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i); - GQuark tag = 0; - gchar *str; - - str = sysprof_symbol_resolver_resolve_with_context (resolver, - ev->frame.time, - ev->frame.pid, - last_context, - address, - &tag); - - if (str != NULL) - { - symbol = g_string_chunk_insert_const (g->symbols, str); - g_free (str); - - if (tag != 0 && !g_hash_table_contains (g->tags, symbol)) - g_hash_table_insert (g->tags, (gchar *)symbol, GSIZE_TO_POINTER (tag)); - - break; - } - } - } - - if (symbol != NULL) - g_array_index (g->resolved, SysprofAddress, len++) = POINTER_TO_U64 (symbol); - } - - if ((cmdline = g_hash_table_lookup (g->cmdlines, GINT_TO_POINTER (ev->frame.pid)))) - g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 (cmdline); - - g_array_index (g->resolved, guint64, len++) = POINTER_TO_U64 ("[Everything]"); - - stack_stash_add_trace (g->stash, - (gpointer)g->resolved->data, - len, - ev->alloc_size); - - g_array_set_size (leak_allocs, leak_allocs->len - 1); - - if (leak_allocs->len == 0) - break; - } - } - else - { - if (!sysprof_capture_reader_skip (g->reader)) - break; - } - } -} - -static void -sysprof_memprof_profile_generate_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - Generate *g = task_data; - - g_assert (G_IS_TASK (task)); - g_assert (g != NULL); - g_assert (g->reader != NULL); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - /* Make sure the capture is at the beginning */ - sysprof_capture_reader_reset (g->reader); - - /* Load all our symbol resolvers */ - for (guint i = 0; i < g->resolvers->len; i++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (g->resolvers, i); - - sysprof_symbol_resolver_load (resolver, g->reader); - sysprof_capture_reader_reset (g->reader); - } - - if (g->mode == SYSPROF_MEMPROF_MODE_ALL_ALLOCS) - { - g_autoptr(SysprofCaptureCursor) cursor = NULL; - - cursor = create_cursor (g->reader); - sysprof_capture_cursor_foreach (cursor, all_allocs_foreach_cb, g); - } - else if (g->mode == SYSPROF_MEMPROF_MODE_TEMP_ALLOCS) - { - temp_allocs_worker (g); - } - else if (g->mode == SYSPROF_MEMPROF_MODE_SUMMARY) - { - summary_worker (g); - } - else if (g->mode == SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS) - { - leaked_allocs_worker (g); - } - - /* Release some data we don't need anymore */ - g_clear_pointer (&g->resolved, g_array_unref); - g_clear_pointer (&g->resolvers, g_ptr_array_unref); - g_clear_pointer (&g->reader, sysprof_capture_reader_unref); - g_clear_pointer (&g->building, stack_stash_unref); - g_clear_pointer (&g->cmdlines, g_hash_table_unref); - g_clear_object (&g->selection); - - g_task_return_boolean (task, TRUE); -} - -static void -sysprof_memprof_profile_generate (SysprofProfile *profile, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SysprofMemprofProfile *self = (SysprofMemprofProfile *)profile; - g_autoptr(GTask) task = NULL; - Generate *g; - - g_assert (SYSPROF_IS_MEMPROF_PROFILE (self)); - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (self, cancellable, callback, user_data); - g_task_set_source_tag (task, sysprof_memprof_profile_generate); - - if (self->reader == NULL) - { - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_NOT_INITIALIZED, - "No capture reader has been set"); - return; - } - - g = g_slice_new0 (Generate); - g->ref_count = 1; - g->reader = sysprof_capture_reader_copy (self->reader); - g->selection = sysprof_selection_copy (self->selection); - g->cmdlines = g_hash_table_new (NULL, NULL); - g->rax = raxNew (); - g->stash = stack_stash_new (NULL); - g->building = stack_stash_new (NULL); - g->resolvers = g_ptr_array_new_with_free_func (g_object_unref); - g->symbols = g_string_chunk_new (4096*4); - g->tags = g_hash_table_new (g_str_hash, g_str_equal); - g->resolved = g_array_new (FALSE, TRUE, sizeof (guint64)); - g->mode = self->mode; - - g_ptr_array_add (g->resolvers, sysprof_capture_symbol_resolver_new ()); - g_ptr_array_add (g->resolvers, sysprof_kernel_symbol_resolver_new ()); - g_ptr_array_add (g->resolvers, sysprof_elf_symbol_resolver_new ()); - - g_task_set_task_data (task, g, (GDestroyNotify) generate_unref); - g_task_run_in_thread (task, sysprof_memprof_profile_generate_worker); -} - -static gboolean -sysprof_memprof_profile_generate_finish (SysprofProfile *profile, - GAsyncResult *result, - GError **error) -{ - SysprofMemprofProfile *self = (SysprofMemprofProfile *)profile; - - g_assert (SYSPROF_IS_MEMPROF_PROFILE (self)); - g_assert (G_IS_TASK (result)); - - g_clear_pointer (&self->g, generate_unref); - - if (g_task_propagate_boolean (G_TASK (result), error)) - { - Generate *g = g_task_get_task_data (G_TASK (result)); - self->g = generate_ref (g); - return TRUE; - } - - return FALSE; -} - -static void -profile_iface_init (SysprofProfileInterface *iface) -{ - iface->set_reader = sysprof_memprof_profile_set_reader; - iface->generate = sysprof_memprof_profile_generate; - iface->generate_finish = sysprof_memprof_profile_generate_finish; -} - -gpointer -sysprof_memprof_profile_get_native (SysprofMemprofProfile *self) -{ - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), NULL); - - if (self->g != NULL) - return self->g->rax; - - return NULL; -} - -gpointer -sysprof_memprof_profile_get_stash (SysprofMemprofProfile *self) -{ - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), NULL); - - if (self->g != NULL) - return self->g->stash; - - return NULL; -} - -gboolean -sysprof_memprof_profile_is_empty (SysprofMemprofProfile *self) -{ - StackNode *root; - - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), FALSE); - - return (self->g == NULL || - self->g->stash == NULL || - !(root = stack_stash_get_root (self->g->stash)) || - !root->total); -} - -GQuark -sysprof_memprof_profile_get_tag (SysprofMemprofProfile *self, - const gchar *symbol) -{ - g_return_val_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self), 0); - - if (self->g != NULL) - return GPOINTER_TO_SIZE (g_hash_table_lookup (self->g->tags, symbol)); - - return 0; -} - -SysprofProfile * -sysprof_memprof_profile_new_with_selection (SysprofSelection *selection) -{ - return g_object_new (SYSPROF_TYPE_MEMPROF_PROFILE, - "selection", selection, - NULL); -} - -void -sysprof_memprof_profile_get_stats (SysprofMemprofProfile *self, - SysprofMemprofStats *stats) -{ - g_return_if_fail (SYSPROF_IS_MEMPROF_PROFILE (self)); - g_return_if_fail (stats != NULL); - - if (self->g != NULL) - *stats = self->g->stats; - else - memset (stats, 0, sizeof *stats); -} diff --git a/src/libsysprof/sysprof-memprof-profile.h b/src/libsysprof/sysprof-memprof-profile.h deleted file mode 100644 index 28e4d915..00000000 --- a/src/libsysprof/sysprof-memprof-profile.h +++ /dev/null @@ -1,98 +0,0 @@ -/* sysprof-memprof-profile.h - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-version-macros.h" - -#include "sysprof-profile.h" -#include "sysprof-selection.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_MEMPROF_PROFILE (sysprof_memprof_profile_get_type()) - -/** - * SysprofMemprofMode: - * @SYSPROF_MEMPROF_MODE_SUMMARY: The summary profile - * @SYSPROF_MEMPROF_MODE_ALL_ALLOCS: find all allocations - * @SYSPROF_MEMPROF_MODE_TEMP_ALLOCS: find temporary allocations - * @SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS: find allocation leaks, Since 3.44 - * - * The memprof profile mode. - * - * Since 3.44 @SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS is available - * to find leaked allocations. - */ -typedef enum -{ - SYSPROF_MEMPROF_MODE_SUMMARY = 0, - SYSPROF_MEMPROF_MODE_ALL_ALLOCS = 1, - SYSPROF_MEMPROF_MODE_TEMP_ALLOCS = 2, - SYSPROF_MEMPROF_MODE_LEAKED_ALLOCS = 3, -} SysprofMemprofMode; - -typedef struct -{ - gint64 n_allocs; - gint64 leaked_allocs; - gint64 leaked_allocs_size; - gint64 temp_allocs; - gint64 temp_allocs_size; - struct { - gint64 bucket; - gint64 n_allocs; - gint64 temp_allocs; - gint64 allocated; - } by_size[14]; - /*< private >*/ - gint64 _reserved[32]; -} SysprofMemprofStats; - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofMemprofProfile, sysprof_memprof_profile, SYSPROF, MEMPROF_PROFILE, GObject) - -SYSPROF_AVAILABLE_IN_3_36 -SysprofProfile *sysprof_memprof_profile_new (void); -SYSPROF_AVAILABLE_IN_3_36 -SysprofProfile *sysprof_memprof_profile_new_with_selection (SysprofSelection *selection); -SYSPROF_AVAILABLE_IN_3_36 -void sysprof_memprof_profile_get_stats (SysprofMemprofProfile *self, - SysprofMemprofStats *stats); -SYSPROF_AVAILABLE_IN_3_36 -SysprofMemprofMode sysprof_memprof_profile_get_mode (SysprofMemprofProfile *self); -SYSPROF_AVAILABLE_IN_3_36 -void sysprof_memprof_profile_set_mode (SysprofMemprofProfile *self, - SysprofMemprofMode mode); -SYSPROF_AVAILABLE_IN_3_36 -gpointer sysprof_memprof_profile_get_native (SysprofMemprofProfile *self); -SYSPROF_AVAILABLE_IN_3_36 -gpointer sysprof_memprof_profile_get_stash (SysprofMemprofProfile *self); -SYSPROF_AVAILABLE_IN_3_36 -gboolean sysprof_memprof_profile_is_empty (SysprofMemprofProfile *self); -SYSPROF_AVAILABLE_IN_3_36 -GQuark sysprof_memprof_profile_get_tag (SysprofMemprofProfile *self, - const gchar *symbol); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-memprof-source.c b/src/libsysprof/sysprof-memprof-source.c deleted file mode 100644 index 790ba5ef..00000000 --- a/src/libsysprof/sysprof-memprof-source.c +++ /dev/null @@ -1,87 +0,0 @@ -/* sysprof-memprof-source.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-memprof-source" - -#include "config.h" - -#include "sysprof-memprof-source.h" - -struct _SysprofMemprofSource -{ - GObject parent_instance; -}; - -static void -sysprof_memprof_source_modify_spawn (SysprofSource *source, - SysprofSpawnable *spawnable) -{ - g_assert (SYSPROF_IS_SOURCE (source)); - g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); - - sysprof_spawnable_setenv (spawnable, "G_SLICE", "always-malloc"); - -#ifdef __linux__ - { - static const gchar so_path[] = PACKAGE_LIBDIR"/libsysprof-memory-"API_VERSION_S".so"; - g_autofree gchar *freeme = NULL; - const gchar *ld_preload; - - if (!(ld_preload = sysprof_spawnable_getenv (spawnable, "LD_PRELOAD"))) - sysprof_spawnable_setenv (spawnable, "LD_PRELOAD", so_path); - else - sysprof_spawnable_setenv (spawnable, - "LD_PRELOAD", - (freeme = g_strdup_printf ("%s:%s", so_path, ld_preload))); - } -#endif -} - -static void -sysprof_memprof_source_stop (SysprofSource *source) -{ - sysprof_source_emit_finished (source); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->modify_spawn = sysprof_memprof_source_modify_spawn; - iface->stop = sysprof_memprof_source_stop; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofMemprofSource, sysprof_memprof_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -sysprof_memprof_source_class_init (SysprofMemprofSourceClass *klass) -{ -} - -static void -sysprof_memprof_source_init (SysprofMemprofSource *self) -{ -} - -SysprofSource * -sysprof_memprof_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_MEMPROF_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-mount-device-private.h b/src/libsysprof/sysprof-mount-device-private.h new file mode 100644 index 00000000..dc01b5d6 --- /dev/null +++ b/src/libsysprof/sysprof-mount-device-private.h @@ -0,0 +1,38 @@ +/* sysprof-mount-device-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MOUNT_DEVICE (sysprof_mount_device_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMountDevice, sysprof_mount_device, SYSPROF, MOUNT_DEVICE, GObject) + +SysprofMountDevice *sysprof_mount_device_new (GRefString *fs_spec, + GRefString *mount_point, + GRefString *subvolume); +const char *sysprof_mount_device_get_fs_spec (SysprofMountDevice *self); +const char *sysprof_mount_device_get_mount_point (SysprofMountDevice *self); +const char *sysprof_mount_device_get_subvolume (SysprofMountDevice *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-mount-device.c b/src/libsysprof/sysprof-mount-device.c new file mode 100644 index 00000000..9dd830a7 --- /dev/null +++ b/src/libsysprof/sysprof-mount-device.c @@ -0,0 +1,152 @@ +/* sysprof-mount-device.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-mount-device-private.h" + +struct _SysprofMountDevice +{ + GObject parent_instance; + GRefString *fs_spec; + GRefString *mount_point; + GRefString *subvolume; +}; + +enum { + PROP_0, + PROP_FS_SPEC, + PROP_MOUNT_POINT, + PROP_SUBVOLUME, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofMountDevice, sysprof_mount_device, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_mount_device_finalize (GObject *object) +{ + SysprofMountDevice *self = (SysprofMountDevice *)object; + + g_clear_pointer (&self->fs_spec, g_ref_string_release); + g_clear_pointer (&self->mount_point, g_ref_string_release); + g_clear_pointer (&self->subvolume, g_ref_string_release); + + G_OBJECT_CLASS (sysprof_mount_device_parent_class)->finalize (object); +} + +static void +sysprof_mount_device_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMountDevice *self = SYSPROF_MOUNT_DEVICE (object); + + switch (prop_id) + { + case PROP_FS_SPEC: + g_value_set_string (value, sysprof_mount_device_get_fs_spec (self)); + break; + + case PROP_MOUNT_POINT: + g_value_set_string (value, sysprof_mount_device_get_mount_point (self)); + break; + + case PROP_SUBVOLUME: + g_value_set_string (value, sysprof_mount_device_get_subvolume (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mount_device_class_init (SysprofMountDeviceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_mount_device_finalize; + object_class->get_property = sysprof_mount_device_get_property; + + properties [PROP_FS_SPEC] = + g_param_spec_string ("fs-spec", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_MOUNT_POINT] = + g_param_spec_string ("mount-point", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SUBVOLUME] = + g_param_spec_string ("subvolume", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_mount_device_init (SysprofMountDevice *self) +{ +} + +SysprofMountDevice * +sysprof_mount_device_new (GRefString *fs_spec, + GRefString *mount_point, + GRefString *subvolume) +{ + SysprofMountDevice *self; + + self = g_object_new (SYSPROF_TYPE_MOUNT_DEVICE, NULL); + self->fs_spec = fs_spec; + self->mount_point = mount_point; + self->subvolume = subvolume; + + return g_steal_pointer (&self); +} + +const char * +sysprof_mount_device_get_fs_spec (SysprofMountDevice *self) +{ + g_return_val_if_fail (SYSPROF_IS_MOUNT_DEVICE (self), NULL); + + return self->fs_spec; +} + +const char * +sysprof_mount_device_get_mount_point (SysprofMountDevice *self) +{ + g_return_val_if_fail (SYSPROF_IS_MOUNT_DEVICE (self), NULL); + + return self->mount_point; +} + +const char * +sysprof_mount_device_get_subvolume (SysprofMountDevice *self) +{ + g_return_val_if_fail (SYSPROF_IS_MOUNT_DEVICE (self), NULL); + + return self->subvolume; +} diff --git a/src/libsysprof/sysprof-mount-namespace-private.h b/src/libsysprof/sysprof-mount-namespace-private.h new file mode 100644 index 00000000..6dd80e32 --- /dev/null +++ b/src/libsysprof/sysprof-mount-namespace-private.h @@ -0,0 +1,43 @@ +/* sysprof-mount-namespace-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-mount-private.h" +#include "sysprof-mount-device-private.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MOUNT_NAMESPACE (sysprof_mount_namespace_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMountNamespace, sysprof_mount_namespace, SYSPROF, MOUNT_NAMESPACE, GObject) + +SysprofMountNamespace *sysprof_mount_namespace_new (void); +SysprofMountNamespace *sysprof_mount_namespace_copy (SysprofMountNamespace *self); +void sysprof_mount_namespace_add_device (SysprofMountNamespace *self, + SysprofMountDevice *mount); +void sysprof_mount_namespace_add_mount (SysprofMountNamespace *self, + SysprofMount *mount); +char **sysprof_mount_namespace_translate (SysprofMountNamespace *self, + const char *path); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-mount-namespace.c b/src/libsysprof/sysprof-mount-namespace.c new file mode 100644 index 00000000..a207e9f8 --- /dev/null +++ b/src/libsysprof/sysprof-mount-namespace.c @@ -0,0 +1,340 @@ +/* sysprof-mount-namespace.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-mount-namespace-private.h" + +struct _SysprofMountNamespace +{ + GObject parent_instance; + GPtrArray *devices; + GPtrArray *mounts; + guint mounts_dirty : 1; +}; + +static GType +sysprof_mount_namespace_get_item_type (GListModel *model) +{ + return SYSPROF_TYPE_MOUNT; +} + +static guint +sysprof_mount_namespace_get_n_items (GListModel *model) +{ + return SYSPROF_MOUNT_NAMESPACE (model)->mounts->len; +} + +static gpointer +sysprof_mount_namespace_get_item (GListModel *model, + guint position) +{ + SysprofMountNamespace *self = SYSPROF_MOUNT_NAMESPACE (model); + + if (position < self->mounts->len) + return g_object_ref (g_ptr_array_index (self->mounts, position)); + + return NULL; +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_mount_namespace_get_item_type; + iface->get_item = sysprof_mount_namespace_get_item; + iface->get_n_items = sysprof_mount_namespace_get_n_items; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofMountNamespace, sysprof_mount_namespace, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static void +sysprof_mount_namespace_finalize (GObject *object) +{ + SysprofMountNamespace *self = (SysprofMountNamespace *)object; + + g_clear_pointer (&self->devices, g_ptr_array_unref); + g_clear_pointer (&self->mounts, g_ptr_array_unref); + + G_OBJECT_CLASS (sysprof_mount_namespace_parent_class)->finalize (object); +} + +static void +sysprof_mount_namespace_class_init (SysprofMountNamespaceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_mount_namespace_finalize; +} + +static void +sysprof_mount_namespace_init (SysprofMountNamespace *self) +{ + self->devices = g_ptr_array_new_with_free_func (g_object_unref); + self->mounts = g_ptr_array_new_with_free_func (g_object_unref); +} + +SysprofMountNamespace * +sysprof_mount_namespace_new (void) +{ + return g_object_new (SYSPROF_TYPE_MOUNT_NAMESPACE, NULL); +} + +SysprofMountNamespace * +sysprof_mount_namespace_copy (SysprofMountNamespace *self) +{ + SysprofMountNamespace *copy; + + g_return_val_if_fail (SYSPROF_IS_MOUNT_NAMESPACE (self), NULL); + + copy = sysprof_mount_namespace_new (); + + for (guint i = 0; i < self->devices->len; i++) + sysprof_mount_namespace_add_device (copy, g_object_ref (g_ptr_array_index (self->devices, i))); + + return copy; +} + +/** + * sysprof_mount_namespace_add_device: + * @self: a #SysprofMountNamespace + * @device: (transfer full): a #SysprofMountDevice + * + * Adds information about where a device is mounted on the host for resolving + * paths to binaries. + */ +void +sysprof_mount_namespace_add_device (SysprofMountNamespace *self, + SysprofMountDevice *device) +{ + g_return_if_fail (SYSPROF_IS_MOUNT_NAMESPACE (self)); + g_return_if_fail (SYSPROF_IS_MOUNT_DEVICE (device)); + + g_ptr_array_add (self->devices, device); +} + +/** + * sysprof_mount_namespace_add_mount: + * @self: a #SysprofMountNamespace + * @mount: (transfer full): a #SysprofMount + * + */ +void +sysprof_mount_namespace_add_mount (SysprofMountNamespace *self, + SysprofMount *mount) +{ + g_return_if_fail (SYSPROF_IS_MOUNT_NAMESPACE (self)); + g_return_if_fail (SYSPROF_IS_MOUNT (mount)); + + g_ptr_array_add (self->mounts, mount); + + self->mounts_dirty = TRUE; +} + +static SysprofMountDevice * +sysprof_mount_namespace_find_device (SysprofMountNamespace *self, + SysprofMount *mount, + const char *relative_path) +{ + const char *mount_source; + g_autofree char *subvolume = NULL; + + g_assert (SYSPROF_IS_MOUNT_NAMESPACE (self)); + g_assert (SYSPROF_IS_MOUNT (mount)); + + while (relative_path[0] == '/') + relative_path++; + + mount_source = sysprof_mount_get_mount_source (mount); + subvolume = sysprof_mount_get_superblock_option (mount, "subvol"); + + for (guint i = 0; i < self->devices->len; i++) + { + SysprofMountDevice *device = g_ptr_array_index (self->devices, i); + const char *fs_spec = sysprof_mount_device_get_fs_spec (device); + + if (g_strcmp0 (fs_spec, mount_source) != 0) + continue; + + if (subvolume != NULL) + { + const char *device_subvolume = sysprof_mount_device_get_subvolume (device); + const char *mount_point = sysprof_mount_device_get_mount_point (device); + + if (g_strcmp0 (subvolume, device_subvolume) != 0) + continue; + + /* Just ignore /sysroot, as it seems to be a convention on systems like + * Silverblue or GNOME OS. + */ + if (g_strcmp0 (mount_point, "/sysroot") == 0) + continue; + } + + return device; + } + + return NULL; +} + +static int +compare_mount (gconstpointer a, + gconstpointer b) +{ + SysprofMount *mount_a = *(SysprofMount * const *)a; + SysprofMount *mount_b = *(SysprofMount * const *)b; + gsize alen = strlen (sysprof_mount_get_mount_point (mount_a)); + gsize blen = strlen (sysprof_mount_get_mount_point (mount_b)); + + if (mount_a->is_overlay && !mount_b->is_overlay) + return -1; + else if (!mount_a->is_overlay && mount_b->is_overlay) + return 1; + + if (alen > blen) + return -1; + else if (blen > alen) + return 1; + + if (mount_a->layer < mount_b->layer) + return -1; + else if (mount_a->layer > mount_b->layer) + return 1; + + return 0; +} + +/** + * sysprof_mount_namespace_translate: + * @self: a #SysprofMountNamespace + * @file: the path within the mount namespace to translate + * + * Attempts to translate a path within the mount namespace into a + * path available in our current mount namespace. + * + * As overlays are involved, multiple paths may be returned which + * could contain the target file. You should check these starting + * from the first element in the resulting array to the last. + * + * Returns: (transfer full) (nullable): a UTF-8 encoded string array + * if successful; otherwise %NULL and @error is set. + */ +char ** +sysprof_mount_namespace_translate (SysprofMountNamespace *self, + const char *file) +{ + g_autoptr(GArray) strv = NULL; + + g_return_val_if_fail (SYSPROF_IS_MOUNT_NAMESPACE (self), NULL); + g_return_val_if_fail (file != NULL, NULL); + + if G_UNLIKELY (self->mounts_dirty) + { + g_ptr_array_sort (self->mounts, compare_mount); + self->mounts_dirty = FALSE; + } + + strv = g_array_new (TRUE, FALSE, sizeof (char *)); + + for (guint i = 0; i < self->mounts->len; i++) + { + SysprofMount *mount = g_ptr_array_index (self->mounts, i); + SysprofMountDevice *device; + const char *device_mount_point; + const char *fs_type; + const char *relative; + char *translated; + + if (!(relative = _sysprof_mount_get_relative_path (mount, file))) + continue; + + fs_type = sysprof_mount_get_filesystem_type (mount); + + if (mount->is_overlay) + { + translated = g_build_filename (mount->mount_source, relative, NULL); + } + else if (g_strcmp0 (fs_type, "overlay") == 0) + { + g_autofree char *lowerdir_str = sysprof_mount_get_superblock_option (mount, "lowerdir"); + g_autofree char *upperdir_str = sysprof_mount_get_superblock_option (mount, "upperdir"); + g_auto(GStrv) lowerdirs = lowerdir_str ? g_strsplit (lowerdir_str, ":", 0) : NULL; + g_auto(GStrv) upperdirs = upperdir_str ? g_strsplit (upperdir_str, ":", 0) : NULL; + + if (upperdirs != NULL) + { + for (guint j = 0; upperdirs[j]; j++) + { + translated = g_build_filename (upperdirs[j], relative, NULL); + g_array_append_val (strv, translated); + } + } + + if (lowerdirs != NULL) + { + for (guint j = 0; lowerdirs[j]; j++) + { + translated = g_build_filename (lowerdirs[j], relative, NULL); + g_array_append_val (strv, translated); + } + } + + continue; + } + else + { + const char *root; + const char *subvolume; + + if (!(device = sysprof_mount_namespace_find_device (self, mount, relative))) + continue; + + device_mount_point = sysprof_mount_device_get_mount_point (device); + root = sysprof_mount_get_root (mount); + subvolume = sysprof_mount_device_get_subvolume (device); + + if (root != NULL && subvolume != NULL) + { + if (g_strcmp0 (root, subvolume) == 0) + root = "/"; + else if (g_str_has_prefix (root, subvolume) && root[strlen (subvolume)] == '/') + root += strlen (subvolume); + } + + translated = g_build_filename (device_mount_point, root, relative, NULL); + } + + g_array_append_val (strv, translated); + } + + if (strv->len == 0) + { + /* Try to passthrough the path in case we had no matches just to give + * things a chance to decode. This can happen if we never recorded + * the contents of /proc/$$/mountinfo. + */ + char *copy = g_strdup (file); + g_array_append_val (strv, copy); + } + + return (char **)g_array_free (g_steal_pointer (&strv), FALSE); +} diff --git a/src/libsysprof/sysprof-mount-private.h b/src/libsysprof/sysprof-mount-private.h new file mode 100644 index 00000000..e0b75dd5 --- /dev/null +++ b/src/libsysprof/sysprof-mount-private.h @@ -0,0 +1,53 @@ +/* sysprof-mount-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-mount.h" +#include "sysprof-strings-private.h" + +G_BEGIN_DECLS + +struct _SysprofMount +{ + GObject parent_instance; + int mount_id; + int parent_mount_id; + int device_major; + int device_minor; + GRefString *root; + GRefString *mount_point; + GRefString *mount_source; + GRefString *filesystem_type; + GRefString *superblock_options; + guint is_overlay : 1; + guint layer : 15; +}; + +SysprofMount *_sysprof_mount_new_for_mountinfo (SysprofStrings *strings, + const char *mountinfo); +SysprofMount *_sysprof_mount_new_for_overlay (SysprofStrings *strings, + const char *mount_point, + const char *host_path, + int layer); +const char *_sysprof_mount_get_relative_path (SysprofMount *self, + const char *path); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-mount.c b/src/libsysprof/sysprof-mount.c new file mode 100644 index 00000000..32733dc9 --- /dev/null +++ b/src/libsysprof/sysprof-mount.c @@ -0,0 +1,370 @@ +/* sysprof-mount.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-mount-private.h" + +enum { + PROP_0, + PROP_DEVICE_MAJOR, + PROP_DEVICE_MINOR, + PROP_FILESYSTEM_TYPE, + PROP_MOUNT_ID, + PROP_MOUNT_POINT, + PROP_MOUNT_SOURCE, + PROP_PARENT_MOUNT_ID, + PROP_ROOT, + PROP_SUPERBLOCK_OPTIONS, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofMount, sysprof_mount, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_mount_finalize (GObject *object) +{ + SysprofMount *self = (SysprofMount *)object; + + g_clear_pointer (&self->root, g_ref_string_release); + g_clear_pointer (&self->mount_point, g_ref_string_release); + g_clear_pointer (&self->mount_source, g_ref_string_release); + g_clear_pointer (&self->filesystem_type, g_ref_string_release); + g_clear_pointer (&self->superblock_options, g_ref_string_release); + + G_OBJECT_CLASS (sysprof_mount_parent_class)->finalize (object); +} + +static void +sysprof_mount_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMount *self = SYSPROF_MOUNT (object); + + switch (prop_id) + { + case PROP_DEVICE_MAJOR: + g_value_set_int (value, sysprof_mount_get_device_major (self)); + break; + + case PROP_DEVICE_MINOR: + g_value_set_int (value, sysprof_mount_get_device_minor (self)); + break; + + case PROP_ROOT: + g_value_set_string (value, sysprof_mount_get_root (self)); + break; + + case PROP_MOUNT_ID: + g_value_set_int (value, sysprof_mount_get_mount_id (self)); + break; + + case PROP_MOUNT_POINT: + g_value_set_string (value, sysprof_mount_get_mount_point (self)); + break; + + case PROP_MOUNT_SOURCE: + g_value_set_string (value, sysprof_mount_get_mount_source (self)); + break; + + case PROP_PARENT_MOUNT_ID: + g_value_set_int (value, sysprof_mount_get_parent_mount_id (self)); + break; + + case PROP_FILESYSTEM_TYPE: + g_value_set_string (value, sysprof_mount_get_filesystem_type (self)); + break; + + case PROP_SUPERBLOCK_OPTIONS: + g_value_set_string (value, sysprof_mount_get_superblock_options (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mount_class_init (SysprofMountClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_mount_finalize; + object_class->get_property = sysprof_mount_get_property; + + properties[PROP_DEVICE_MAJOR] = + g_param_spec_int ("device-major", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_DEVICE_MINOR] = + g_param_spec_int ("device-minor", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_ROOT] = + g_param_spec_string ("root", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MOUNT_ID] = + g_param_spec_int ("mount-id", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_PARENT_MOUNT_ID] = + g_param_spec_int ("parent-mount-id", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MOUNT_POINT] = + g_param_spec_string ("mount-point", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MOUNT_SOURCE] = + g_param_spec_string ("mount-source", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_FILESYSTEM_TYPE] = + g_param_spec_string ("filesystem-type", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_SUPERBLOCK_OPTIONS] = + g_param_spec_string ("superblock-options", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_mount_init (SysprofMount *self) +{ +} + +SysprofMount * +_sysprof_mount_new_for_mountinfo (SysprofStrings *strings, + const char *mountinfo) +{ + g_autoptr(SysprofMount) self = NULL; + g_auto(GStrv) parts = NULL; + gsize n_parts; + guint i; + + g_return_val_if_fail (strings != NULL, NULL); + g_return_val_if_fail (mountinfo != NULL, NULL); + + parts = g_strsplit (mountinfo, " ", 20); + n_parts = g_strv_length (parts); + if (n_parts < 10) + return NULL; + + self = g_object_new (SYSPROF_TYPE_MOUNT, NULL); + + self->mount_id = g_ascii_strtoll (parts[0], NULL, 10); + self->parent_mount_id = g_ascii_strtoll (parts[1], NULL, 10); + sscanf (parts[2], "%d:%d", &self->device_major, &self->device_minor); + self->root = sysprof_strings_get (strings, parts[3]); + self->mount_point = sysprof_strings_get (strings, parts[4]); + + /* Skip forward to end of optional fields */ + for (i = 5; parts[i]; i++) + { + if (strcmp (parts[i], "-") == 0) + break; + } + + if (parts[i] == NULL) + goto finish; + + /* filesystem-type column */ + i++; + if (parts[i] == NULL) + goto finish; + self->filesystem_type = sysprof_strings_get (strings, parts[i]); + + /* mount-source column */ + i++; + if (parts[i] == NULL) + goto finish; + self->mount_source = sysprof_strings_get (strings, parts[i]); + + /* superblock options column */ + i++; + if (parts[i] == NULL) + goto finish; + self->superblock_options = sysprof_strings_get (strings, parts[i]); + +finish: + return g_steal_pointer (&self); +} + +SysprofMount * +_sysprof_mount_new_for_overlay (SysprofStrings *strings, + const char *mount_point, + const char *host_path, + int layer) +{ + SysprofMount *self; + + g_return_val_if_fail (strings != NULL, NULL); + g_return_val_if_fail (mount_point != NULL, NULL); + g_return_val_if_fail (host_path != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_MOUNT, NULL); + self->mount_point = sysprof_strings_get (strings, mount_point); + self->root = sysprof_strings_get (strings, "/"); + self->mount_source = sysprof_strings_get (strings, host_path); + self->is_overlay = TRUE; + + return self; +} + +int +sysprof_mount_get_device_major (SysprofMount *self) +{ + return self->device_major; +} + +int +sysprof_mount_get_device_minor (SysprofMount *self) +{ + return self->device_minor; +} + +const char * +sysprof_mount_get_root (SysprofMount *self) +{ + return self->root; +} + +const char * +sysprof_mount_get_mount_point (SysprofMount *self) +{ + return self->mount_point; +} + +const char * +sysprof_mount_get_mount_source (SysprofMount *self) +{ + return self->mount_source; +} + +const char * +sysprof_mount_get_filesystem_type (SysprofMount *self) +{ + return self->filesystem_type; +} + +const char * +sysprof_mount_get_superblock_options (SysprofMount *self) +{ + return self->superblock_options; +} + +char * +sysprof_mount_get_superblock_option (SysprofMount *self, + const char *option) +{ + gsize option_len; + + g_return_val_if_fail (SYSPROF_IS_MOUNT (self), NULL); + g_return_val_if_fail (option != NULL, NULL); + + if (self->superblock_options == NULL) + return NULL; + + option_len = strlen (option); + + for (const char *c = strstr (self->superblock_options, option); + c != NULL; + c = strstr (c+1, option)) + { + if ((c == self->superblock_options || c[-1] == ',') && + (c[option_len] == 0 || c[option_len] == '=')) + { + const char *value; + const char *endptr; + + if (c[option_len] == 0) + return g_strdup (""); + + value = &c[option_len+1]; + + if (!(endptr = strchr (value, ','))) + return g_strdup (value); + + return g_strndup (value, endptr - value); + } + } + + return NULL; +} + +int +sysprof_mount_get_mount_id (SysprofMount *self) +{ + return self->mount_id; +} + +int +sysprof_mount_get_parent_mount_id (SysprofMount *self) +{ + return self->parent_mount_id; +} + +static inline gboolean +mount_is_root (SysprofMount *self) +{ + return self->mount_point[0] == '/' && self->mount_point[1] == 0; +} + +const char * +_sysprof_mount_get_relative_path (SysprofMount *self, + const char *path) +{ + gsize len; + + g_return_val_if_fail (SYSPROF_IS_MOUNT (self), NULL); + + if (path == NULL || self->mount_point == NULL) + return NULL; + + len = g_ref_string_length (self->mount_point); + + /* We don't care about directory paths, so ensure that we both + * have the proper prefix and that it is in a subdirectory of this + * mount point. + */ + if (!mount_is_root (self) && + (!g_str_has_prefix (path, self->mount_point) || path[len] != '/')) + return NULL; + + return &path[len]; +} diff --git a/src/libsysprof/sysprof-mount.h b/src/libsysprof/sysprof-mount.h new file mode 100644 index 00000000..91e11c8f --- /dev/null +++ b/src/libsysprof/sysprof-mount.h @@ -0,0 +1,56 @@ +/* sysprof-mount.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MOUNT (sysprof_mount_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofMount, sysprof_mount, SYSPROF, MOUNT, GObject) + +SYSPROF_AVAILABLE_IN_ALL +int sysprof_mount_get_device_major (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_mount_get_device_minor (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_mount_get_mount_id (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_mount_get_parent_mount_id (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_mount_get_root (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_mount_get_mount_point (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_mount_get_mount_source (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_mount_get_filesystem_type (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_mount_get_superblock_options (SysprofMount *self); +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_mount_get_superblock_option (SysprofMount *self, + const char *option); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-mountinfo.c b/src/libsysprof/sysprof-mountinfo.c deleted file mode 100644 index 826ce8d6..00000000 --- a/src/libsysprof/sysprof-mountinfo.c +++ /dev/null @@ -1,352 +0,0 @@ -/* sysprof-mountinfo.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-mountinfo" - -#include "config.h" - -#include "sysprof-mountinfo.h" - -typedef struct -{ - gchar *device; - gchar *mountpoint; - gchar *subvol; -} Mount; - -typedef struct -{ - gchar *host_path; - gchar *mount_path; -} Mountinfo; - -struct _SysprofMountinfo -{ - GArray *mounts; - GArray *mountinfos; - GHashTable *dircache; -}; - -enum { - COLUMN_MOUNT_ID = 0, - COLUMN_MOUNT_PARENT_ID, - COLUMN_MAJOR_MINOR, - COLUMN_ROOT, - COLUMN_MOUNT_POINT, - COLUMN_MOUNT_OPTIONS, -}; - -static void -mount_clear (gpointer data) -{ - Mount *m = data; - - g_free (m->device); - g_free (m->mountpoint); - g_free (m->subvol); -} - -static void -mountinfo_clear (gpointer data) -{ - Mountinfo *m = data; - - g_free (m->host_path); - g_free (m->mount_path); -} - -SysprofMountinfo * -sysprof_mountinfo_new (void) -{ - SysprofMountinfo *self; - - self = g_slice_new0 (SysprofMountinfo); - self->mounts = g_array_new (FALSE, FALSE, sizeof (Mount)); - g_array_set_clear_func (self->mounts, mount_clear); - self->mountinfos = g_array_new (FALSE, FALSE, sizeof (Mountinfo)); - g_array_set_clear_func (self->mountinfos, mountinfo_clear); - self->dircache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); - - return g_steal_pointer (&self); -} - -void -sysprof_mountinfo_free (SysprofMountinfo *self) -{ - g_clear_pointer (&self->mounts, g_array_unref); - g_clear_pointer (&self->mountinfos, g_array_unref); - g_clear_pointer (&self->dircache, g_hash_table_unref); - g_slice_free (SysprofMountinfo, self); -} - -gchar * -sysprof_mountinfo_translate (SysprofMountinfo *self, - const gchar *path) -{ - g_autofree gchar *dir = NULL; - const gchar *translate; - - g_assert (self != NULL); - - if (path == NULL) - return NULL; - - /* First try the dircache by looking up the translated parent - * directory and appending basename to it. - */ - dir = g_path_get_dirname (path); - - if ((translate = g_hash_table_lookup (self->dircache, dir))) - { - g_autofree gchar *name = g_path_get_basename (path); - return g_build_filename (translate, name, NULL); - } - - for (guint i = 0; i < self->mountinfos->len; i++) - { - const Mountinfo *m = &g_array_index (self->mountinfos, Mountinfo, i); - - if (g_str_has_prefix (path, m->mount_path)) - { - gchar *ret; - - ret = g_build_filename (m->host_path, path + strlen (m->mount_path), NULL); - g_hash_table_insert (self->dircache, - g_steal_pointer (&dir), - g_path_get_dirname (ret)); - return ret; - } - } - - return NULL; -} - -static void -decode_space (gchar **str) -{ - /* Replace encoded space "\040" with ' ' */ - if (strstr (*str, "\\040")) - { - g_auto(GStrv) parts = g_strsplit (*str, "\\040", 0); - g_free (*str); - *str = g_strjoinv (" ", parts); - } -} - -void -sysprof_mountinfo_parse_mounts (SysprofMountinfo *self, - const gchar *contents) -{ - g_auto(GStrv) lines = NULL; - - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (contents != NULL); - - lines = g_strsplit (contents, "\n", 0); - - for (guint i = 0; lines[i]; i++) - { - g_auto(GStrv) parts = g_strsplit (lines[i], " ", 5); - g_autofree char *subvol = NULL; - const char *filesystem; - const char *mountpoint; - const char *device; - const char *options; - Mount m; - - /* Field 0: device - * Field 1: mountpoint - * Field 2: filesystem - * Field 3: Options - * .. Ignored .. - */ - if (g_strv_length (parts) != 5) - continue; - - for (guint j = 0; parts[j]; j++) - decode_space (&parts[j]); - - device = parts[0]; - mountpoint = parts[1]; - filesystem = parts[2]; - options = parts[3]; - - if (g_strcmp0 (filesystem, "btrfs") == 0) - { - g_auto(GStrv) opts = g_strsplit (options, ",", 0); - - for (guint k = 0; opts[k]; k++) - { - if (g_str_has_prefix (opts[k], "subvol=")) - { - subvol = g_strdup (opts[k] + strlen ("subvol=")); - break; - } - } - } - - m.device = g_strdup (device); - m.mountpoint = g_strdup (mountpoint); - m.subvol = g_steal_pointer (&subvol); - - g_array_append_val (self->mounts, m); - } -} - -void -sysprof_mountinfo_reset (SysprofMountinfo *self) -{ - g_assert (self != NULL); - g_assert (self->mountinfos != NULL); - - /* Keep mounts, but release mountinfos */ - if (self->mountinfos->len) - g_array_remove_range (self->mountinfos, 0, self->mountinfos->len); - g_hash_table_remove_all (self->dircache); -} - -static const gchar * -get_device_mount (SysprofMountinfo *self, - const gchar *device) -{ - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (device != NULL); - - for (guint i = 0; i < self->mounts->len; i++) - { - const Mount *m = &g_array_index (self->mounts, Mount, i); - - if (strcmp (device, m->device) == 0) - return m->mountpoint; - } - - return NULL; -} - -static void -sysprof_mountinfo_parse_mountinfo_line (SysprofMountinfo *self, - const gchar *line) -{ - g_auto(GStrv) parts = NULL; - const gchar *prefix; - const gchar *src; - Mountinfo m; - gsize n_parts; - guint i; - - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (self->mountinfos != NULL); - - parts = g_strsplit (line, " ", 0); - n_parts = g_strv_length (parts); - - if (n_parts < 10) - return; - - /* The device identifier is the 2nd column after "-" */ - for (i = 5; i < n_parts; i++) - { - if (strcmp (parts[i], "-") == 0) - break; - } - if (i >= n_parts || parts[i][0] != '-' || parts[i+1] == NULL || parts[i+2] == NULL) - return; - - prefix = get_device_mount (self, parts[i+2]); - src = parts[COLUMN_ROOT]; - - /* If this references a subvolume, try to find the mount by matching - * the subvolumne using the "src". This isn't exactly correct, but it's - * good enough to get btrfs stuff working for common installs. - */ - if (g_strcmp0 (parts[8], "btrfs") == 0) - { - const char *subvol = src; - - for (i = 0; i < self->mounts->len; i++) - { - const Mount *mnt = &g_array_index (self->mounts, Mount, i); - - if (g_strcmp0 (mnt->subvol, subvol) == 0) - { - src = mnt->mountpoint; - break; - } - } - } - - while (*src == '/') - src++; - - if (*src == 0) - return; - - if (prefix != NULL) - m.host_path = g_build_filename (prefix, src, NULL); - else - m.host_path = g_strdup (src); - - m.mount_path = g_strdup (parts[COLUMN_MOUNT_POINT]); - g_array_append_val (self->mountinfos, m); -} - -static gint -sort_by_length (gconstpointer a, - gconstpointer b) -{ - const Mountinfo *mpa = a; - const Mountinfo *mpb = b; - gsize alen = strlen (mpa->mount_path); - gsize blen = strlen (mpb->mount_path); - - if (alen > blen) - return -1; - else if (blen > alen) - return 1; - else - return 0; -} - -void -sysprof_mountinfo_parse_mountinfo (SysprofMountinfo *self, - const gchar *contents) -{ - g_auto(GStrv) lines = NULL; - - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (self->mountinfos != NULL); - - lines = g_strsplit (contents, "\n", 0); - - for (guint i = 0; lines[i]; i++) - sysprof_mountinfo_parse_mountinfo_line (self, lines[i]); - - g_array_sort (self->mountinfos, sort_by_length); - - for (guint i = 0; i < self->mountinfos->len; i++) - { - const Mountinfo *m = &g_array_index (self->mountinfos, Mountinfo, i); - g_print ("MM %s => %s\n", m->host_path, m->mount_path); - } -} diff --git a/src/libsysprof/sysprof-mountinfo.h b/src/libsysprof/sysprof-mountinfo.h deleted file mode 100644 index 85c661e2..00000000 --- a/src/libsysprof/sysprof-mountinfo.h +++ /dev/null @@ -1,41 +0,0 @@ -/* sysprof-mountinfo.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef struct _SysprofMountinfo SysprofMountinfo; - -SysprofMountinfo *sysprof_mountinfo_new (void); -void sysprof_mountinfo_parse_mounts (SysprofMountinfo *self, - const gchar *contents); -void sysprof_mountinfo_parse_mountinfo (SysprofMountinfo *self, - const gchar *contents); -void sysprof_mountinfo_reset (SysprofMountinfo *self); -gchar *sysprof_mountinfo_translate (SysprofMountinfo *self, - const gchar *path); -void sysprof_mountinfo_free (SysprofMountinfo *self); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMountinfo, sysprof_mountinfo_free) - -G_END_DECLS diff --git a/src/libsysprof/sysprof-multi-symbolizer.c b/src/libsysprof/sysprof-multi-symbolizer.c new file mode 100644 index 00000000..483cdde5 --- /dev/null +++ b/src/libsysprof/sysprof-multi-symbolizer.c @@ -0,0 +1,193 @@ +/* sysprof-multi-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-multi-symbolizer.h" +#include "sysprof-symbolizer-private.h" + +struct _SysprofMultiSymbolizer +{ + SysprofSymbolizer parent_instance; + GPtrArray *symbolizers; +}; + +struct _SysprofMultiSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofMultiSymbolizer, sysprof_multi_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +static void +sysprof_multi_symbolizer_prepare_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofSymbolizer *symbolizer = (SysprofSymbolizer *)object; + g_autoptr(GTask) task = user_data; + g_autoptr(GError) error = NULL; + GPtrArray *state; + + g_assert (SYSPROF_IS_SYMBOLIZER (symbolizer)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (G_IS_TASK (task)); + + state = g_task_get_task_data (task); + + g_assert (state != NULL); + g_assert (state->len > 0); + + if (!_sysprof_symbolizer_prepare_finish (symbolizer, result, &error)) + g_warning ("Failed to initialize symbolizer: %s", error->message); + + g_ptr_array_remove (state, symbolizer); + + if (state->len == 0) + g_task_return_boolean (task, TRUE); +} + +static void +sysprof_multi_symbolizer_prepare_async (SysprofSymbolizer *symbolizer, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SysprofMultiSymbolizer *self = (SysprofMultiSymbolizer *)symbolizer; + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) state = NULL; + + g_assert (SYSPROF_IS_MULTI_SYMBOLIZER (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + state = g_ptr_array_new_with_free_func (g_object_unref); + for (guint i = 0; i < self->symbolizers->len; i++) + g_ptr_array_add (state, g_object_ref (g_ptr_array_index (self->symbolizers, i))); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_multi_symbolizer_prepare_async); + g_task_set_task_data (task, g_ptr_array_ref (state), (GDestroyNotify)g_ptr_array_unref); + + if (state->len == 0) + { + g_task_return_boolean (task, TRUE); + return; + } + + for (guint i = 0; i < state->len; i++) + { + SysprofSymbolizer *child = g_ptr_array_index (state, i); + + _sysprof_symbolizer_prepare_async (child, + document, + cancellable, + sysprof_multi_symbolizer_prepare_cb, + g_object_ref (task)); + } +} + +static gboolean +sysprof_multi_symbolizer_prepare_finish (SysprofSymbolizer *symbolizer, + GAsyncResult *result, + GError **error) +{ + g_assert (SYSPROF_IS_MULTI_SYMBOLIZER (symbolizer)); + g_assert (G_IS_TASK (result)); + g_assert (g_task_is_valid (result, symbolizer)); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +static SysprofSymbol * +sysprof_multi_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + SysprofMultiSymbolizer *self = SYSPROF_MULTI_SYMBOLIZER (symbolizer); + + for (guint i = 0; i < self->symbolizers->len; i++) + { + SysprofSymbolizer *child = g_ptr_array_index (self->symbolizers, i); + SysprofSymbol *symbol = _sysprof_symbolizer_symbolize (child, strings, process_info, context, address); + + if (symbol != NULL) + return symbol; + } + + return NULL; +} + +static void +sysprof_multi_symbolizer_finalize (GObject *object) +{ + SysprofMultiSymbolizer *self = (SysprofMultiSymbolizer *)object; + + g_clear_pointer (&self->symbolizers, g_ptr_array_unref); + + G_OBJECT_CLASS (sysprof_multi_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_multi_symbolizer_class_init (SysprofMultiSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + object_class->finalize = sysprof_multi_symbolizer_finalize; + + symbolizer_class->prepare_async = sysprof_multi_symbolizer_prepare_async; + symbolizer_class->prepare_finish = sysprof_multi_symbolizer_prepare_finish; + symbolizer_class->symbolize = sysprof_multi_symbolizer_symbolize; +} + +static void +sysprof_multi_symbolizer_init (SysprofMultiSymbolizer *self) +{ + self->symbolizers = g_ptr_array_new_with_free_func (g_object_unref); +} + +SysprofMultiSymbolizer * +sysprof_multi_symbolizer_new (void) +{ + return g_object_new (SYSPROF_TYPE_MULTI_SYMBOLIZER, NULL); +} + +/** + * sysprof_multi_symbolizer_add: + * @self: a #SysprofMultiSymbolizer + * @symbolizer: (transfer full): a #SysprofSymbolizer + * + * Takes ownership of @symbolizer and adds it to the list of symbolizers + * that will be queried when @self is queried for symbols. + */ +void +sysprof_multi_symbolizer_take (SysprofMultiSymbolizer *self, + SysprofSymbolizer *symbolizer) +{ + g_return_if_fail (SYSPROF_IS_MULTI_SYMBOLIZER (self)); + g_return_if_fail (SYSPROF_IS_SYMBOLIZER (symbolizer)); + g_return_if_fail ((gpointer)self != (gpointer)symbolizer); + + g_ptr_array_add (self->symbolizers, symbolizer); +} diff --git a/src/libsysprof/sysprof-multi-symbolizer.h b/src/libsysprof/sysprof-multi-symbolizer.h new file mode 100644 index 00000000..2ac53f47 --- /dev/null +++ b/src/libsysprof/sysprof-multi-symbolizer.h @@ -0,0 +1,45 @@ +/* sysprof-multi-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MULTI_SYMBOLIZER (sysprof_multi_symbolizer_get_type()) +#define SYSPROF_IS_MULTI_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_MULTI_SYMBOLIZER) +#define SYSPROF_MULTI_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_MULTI_SYMBOLIZER, SysprofMultiSymbolizer) +#define SYSPROF_MULTI_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_MULTI_SYMBOLIZER, SysprofMultiSymbolizerClass) + +typedef struct _SysprofMultiSymbolizer SysprofMultiSymbolizer; +typedef struct _SysprofMultiSymbolizerClass SysprofMultiSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_multi_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofMultiSymbolizer *sysprof_multi_symbolizer_new (void); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_multi_symbolizer_take (SysprofMultiSymbolizer *self, + SysprofSymbolizer *symbolizer); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMultiSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-netdev-source.c b/src/libsysprof/sysprof-netdev-source.c deleted file mode 100644 index 149c360d..00000000 --- a/src/libsysprof/sysprof-netdev-source.c +++ /dev/null @@ -1,420 +0,0 @@ -/* sysprof-netdev-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-netdev-source" - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "sysprof-backport-autocleanups.h" -#include "sysprof-line-reader.h" -#include "sysprof-netdev-source.h" -#include "sysprof-helpers.h" - -struct _SysprofNetdevSource -{ - GObject parent_instance; - - SysprofCaptureWriter *writer; - GArray *netdevs; - - /* Combined (all devices) rx/tx counters */ - guint combined_rx_id; - guint combined_tx_id; - - /* FD for /proc/net/dev contents */ - gint netdev_fd; - - /* GSource ID for polling */ - guint poll_source; -}; - -typedef struct -{ - /* Counter IDs */ - guint rx_bytes_id; - guint tx_bytes_id; - gchar iface[32]; - gint64 rx_bytes; - gint64 rx_packets; - gint64 rx_errors; - gint64 rx_dropped; - gint64 rx_fifo; - gint64 rx_frame; - gint64 rx_compressed; - gint64 rx_multicast; - gint64 tx_bytes; - gint64 tx_packets; - gint64 tx_errors; - gint64 tx_dropped; - gint64 tx_fifo; - gint64 tx_collisions; - gint64 tx_carrier; - gint64 tx_compressed; -} Netdev; - -static void source_iface_init (SysprofSourceInterface *); - -G_DEFINE_TYPE_WITH_CODE (SysprofNetdevSource, sysprof_netdev_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static Netdev * -find_device_by_name (SysprofNetdevSource *self, - const gchar *name) -{ - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - g_assert (self->writer != NULL); - g_assert (name != NULL); - - for (guint i = 0; i < self->netdevs->len; i++) - { - Netdev *netdev = &g_array_index (self->netdevs, Netdev, i); - - if (strcmp (name, netdev->iface) == 0) - return netdev; - } - - return NULL; -} - -static Netdev * -register_counters_by_name (SysprofNetdevSource *self, - const gchar *name) -{ - SysprofCaptureCounter ctr[2] = {{{0}}}; - g_autofree gchar *rx = NULL; - g_autofree gchar *tx = NULL; - Netdev nd = {0}; - - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - g_assert (name != NULL); - g_assert (self->writer != NULL); - - rx = g_strdup_printf ("RX Bytes (%s)", name); - tx = g_strdup_printf ("TX Bytes (%s)", name); - - nd.rx_bytes_id = sysprof_capture_writer_request_counter (self->writer, 1); - nd.tx_bytes_id = sysprof_capture_writer_request_counter (self->writer, 1); - g_strlcpy (nd.iface, name, sizeof nd.iface); - g_array_append_val (self->netdevs, nd); - - g_strlcpy (ctr[0].category, "Network", sizeof ctr[0].category); - g_strlcpy (ctr[0].name, rx, sizeof ctr[0].name); - g_strlcpy (ctr[0].description, name, sizeof ctr[0].description); - ctr[0].id = nd.rx_bytes_id; - ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; - ctr[0].value.v64 = 0; - - g_strlcpy (ctr[1].category, "Network", sizeof ctr[1].category); - g_strlcpy (ctr[1].name, tx, sizeof ctr[1].name); - g_strlcpy (ctr[1].description, name, sizeof ctr[1].description); - ctr[1].id = nd.tx_bytes_id; - ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; - ctr[1].value.v64 = 0; - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - ctr, G_N_ELEMENTS (ctr)); - - return &g_array_index (self->netdevs, Netdev, self->netdevs->len - 1); -} - -static gboolean -sysprof_netdev_source_get_is_ready (SysprofSource *source) -{ - return TRUE; -} - -static void -sysprof_netdev_source_prepare (SysprofSource *source) -{ - SysprofNetdevSource *self = (SysprofNetdevSource *)source; - g_autoptr(GError) error = NULL; - SysprofCaptureCounter ctr[2] = {{{0}}}; - - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - - self->netdev_fd = g_open ("/proc/net/dev", O_RDONLY, 0); - - if (self->netdev_fd == -1) - { - int errsv = errno; - error = g_error_new (G_FILE_ERROR, - g_file_error_from_errno (errsv), - "%s", - g_strerror (errsv)); - sysprof_source_emit_failed (source, error); - return; - } - - self->combined_rx_id = sysprof_capture_writer_request_counter (self->writer, 1); - self->combined_tx_id = sysprof_capture_writer_request_counter (self->writer, 1); - - g_strlcpy (ctr[0].category, "Network", sizeof ctr[0].category); - g_strlcpy (ctr[0].name, "RX Bytes", sizeof ctr[0].name); - g_strlcpy (ctr[0].description, "Combined", sizeof ctr[0].description); - ctr[0].id = self->combined_rx_id; - ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; - ctr[0].value.v64 = 0; - - g_strlcpy (ctr[1].category, "Network", sizeof ctr[1].category); - g_strlcpy (ctr[1].name, "TX Bytes", sizeof ctr[1].name); - g_strlcpy (ctr[1].description, "Combined", sizeof ctr[1].description); - ctr[1].id = self->combined_tx_id; - ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; - ctr[1].value.v64 = 0; - - sysprof_capture_writer_define_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - ctr, G_N_ELEMENTS (ctr)); - - sysprof_source_emit_ready (source); -} - -static gboolean -sysprof_netdev_source_poll_cb (gpointer data) -{ - g_autoptr(SysprofLineReader) reader = NULL; - SysprofNetdevSource *self = data; - g_autoptr(GArray) counters = NULL; - g_autoptr(GArray) values = NULL; - gchar buf[4096*4]; - gint64 combined_rx = 0; - gint64 combined_tx = 0; - gssize len; - gsize line_len; - gchar *line; - - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - - if (self->netdev_fd == -1) - { - self->poll_source = 0; - return G_SOURCE_REMOVE; - } - - /* Seek to 0 forces reload of data */ - lseek (self->netdev_fd, 0, SEEK_SET); - - len = read (self->netdev_fd, buf, sizeof buf - 1); - - /* Bail for now unless we read enough data */ - if (len > 0) - buf[len] = 0; - else - return G_SOURCE_CONTINUE; - - counters = g_array_new (FALSE, FALSE, sizeof (guint)); - values = g_array_new (FALSE, FALSE, sizeof (SysprofCaptureCounterValue)); - reader = sysprof_line_reader_new (buf, len); - -#if 0 -Entries looks like this... ------------------------------------------------------------------------------------------------------------------------------- -Inter-| Receive | Transmit - face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed - lo: 10410 104 0 0 0 0 0 0 10410 104 0 0 0 0 0 0 - eth0:1069675772 4197670 0 0 0 0 0 0 3221945712 3290571 0 0 0 0 0 0 ------------------------------------------------------------------------------------------------------------------------------- -#endif - - /* Skip first two lines */ - for (guint i = 0; i < 2; i++) - { - if (!(line = (gchar *)sysprof_line_reader_next (reader, &line_len))) - return G_SOURCE_CONTINUE; - } - - while ((line = (gchar *)sysprof_line_reader_next (reader, &line_len))) - { - Netdev *nd; - gchar *name; - gchar *ptr = line; - - line[line_len] = 0; - - for (; *ptr && g_ascii_isspace (*ptr); ptr++) { /* Do Nothing */ } - name = ptr; - for (; *ptr && *ptr != ':'; ptr++) { /* Do Nothing */ } - *ptr = 0; - - if (!(nd = find_device_by_name (self, name))) - nd = register_counters_by_name (self, name); - - ptr++; - - sscanf (ptr, - "%"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT - " %"G_GINT64_FORMAT, - &nd->rx_bytes, - &nd->rx_packets, - &nd->rx_errors, - &nd->rx_dropped, - &nd->rx_fifo, - &nd->rx_frame, - &nd->rx_compressed, - &nd->rx_multicast, - &nd->tx_bytes, - &nd->tx_packets, - &nd->tx_errors, - &nd->tx_dropped, - &nd->tx_fifo, - &nd->tx_collisions, - &nd->tx_carrier, - &nd->tx_compressed); - - combined_rx += nd->rx_bytes; - combined_tx += nd->tx_bytes; - - g_array_append_val (counters, nd->rx_bytes_id); - g_array_append_val (values, nd->rx_bytes); - - g_array_append_val (counters, nd->tx_bytes_id); - g_array_append_val (values, nd->tx_bytes); - } - - g_array_append_val (counters, self->combined_rx_id); - g_array_append_val (values, combined_rx); - - g_array_append_val (counters, self->combined_tx_id); - g_array_append_val (values, combined_tx); - - sysprof_capture_writer_set_counters (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - (const guint *)(gpointer)counters->data, - (const SysprofCaptureCounterValue *)(gpointer)values->data, - counters->len); - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_netdev_source_start (SysprofSource *source) -{ - SysprofNetdevSource *self = (SysprofNetdevSource *)source; - - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - - self->poll_source = g_timeout_add (200, sysprof_netdev_source_poll_cb, self); - - /* Poll immediately */ - sysprof_netdev_source_poll_cb (self); -} - -static void -sysprof_netdev_source_stop (SysprofSource *source) -{ - SysprofNetdevSource *self = (SysprofNetdevSource *)source; - - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - - /* Poll one last time */ - sysprof_netdev_source_poll_cb (self); - - g_clear_handle_id (&self->poll_source, g_source_remove); - - sysprof_source_emit_finished (source); -} - -static void -sysprof_netdev_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofNetdevSource *self = (SysprofNetdevSource *)source; - - g_assert (SYSPROF_IS_NETDEV_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->get_is_ready = sysprof_netdev_source_get_is_ready; - iface->prepare = sysprof_netdev_source_prepare; - iface->set_writer = sysprof_netdev_source_set_writer; - iface->start = sysprof_netdev_source_start; - iface->stop = sysprof_netdev_source_stop; -} - -static void -sysprof_netdev_source_finalize (GObject *object) -{ - SysprofNetdevSource *self = (SysprofNetdevSource *)object; - - g_clear_pointer (&self->netdevs, g_array_unref); - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - - if (self->netdev_fd != -1) - { - close (self->netdev_fd); - self->netdev_fd = -1; - } - - G_OBJECT_CLASS (sysprof_netdev_source_parent_class)->finalize (object); -} - -static void -sysprof_netdev_source_class_init (SysprofNetdevSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_netdev_source_finalize; -} - -static void -sysprof_netdev_source_init (SysprofNetdevSource *self) -{ - self->netdevs = g_array_new (FALSE, FALSE, sizeof (Netdev)); -} - -SysprofSource * -sysprof_netdev_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_NETDEV_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-network-usage.c b/src/libsysprof/sysprof-network-usage.c new file mode 100644 index 00000000..92007c4b --- /dev/null +++ b/src/libsysprof/sysprof-network-usage.c @@ -0,0 +1,366 @@ +/* sysprof-network-usage.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "line-reader-private.h" + +#include "sysprof-network-usage.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +struct _SysprofNetworkUsage +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofNetworkUsageClass +{ + SysprofInstrumentClass parent_class; +}; + +typedef struct _DeviceUsage +{ + /* Counter IDs */ + guint rx_bytes_id; + guint tx_bytes_id; + gchar iface[32]; + gint64 rx_bytes; + gint64 rx_packets; + gint64 rx_errors; + gint64 rx_dropped; + gint64 rx_fifo; + gint64 rx_frame; + gint64 rx_compressed; + gint64 rx_multicast; + gint64 tx_bytes; + gint64 tx_packets; + gint64 tx_errors; + gint64 tx_dropped; + gint64 tx_fifo; + gint64 tx_collisions; + gint64 tx_carrier; + gint64 tx_compressed; +} DeviceUsage; + +G_DEFINE_FINAL_TYPE (SysprofNetworkUsage, sysprof_network_usage, SYSPROF_TYPE_INSTRUMENT) + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; +} Record; + +static void +record_free (gpointer data) +{ + Record *record = data; + + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_free (record); +} + +static DeviceUsage * +find_device_by_name (GArray *ar, + const char *name) +{ + for (guint i = 0; i < ar->len; i++) + { + DeviceUsage *dev = &g_array_index (ar, DeviceUsage, i); + + if (g_strcmp0 (name, dev->iface) == 0) + return dev; + } + + return NULL; +} + +static DexFuture * +sysprof_network_usage_record_fiber (gpointer user_data) +{ + g_autoptr(GByteArray) buf = g_byte_array_new (); + Record *record = user_data; + g_autofree SysprofCaptureCounterValue *values = NULL; + g_autofree guint *ids = NULL; + g_autoptr(GArray) devices = NULL; + g_autoptr(GError) error = NULL; + SysprofCaptureWriter *writer; + SysprofCaptureCounter ctr[2] = {0}; + g_autofd int stat_fd = -1; + LineReader reader; + guint combined_rx_id; + guint combined_tx_id; + gssize n_read; + gsize line_len; + guint lineno; + char *line; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + g_byte_array_set_size (buf, 4096*2); + + writer = _sysprof_recording_writer (record->recording); + + if (-1 == (stat_fd = open ("/proc/net/dev", O_RDONLY|O_CLOEXEC))) + return dex_future_new_for_errno (errno); + + devices = g_array_new (FALSE, FALSE, sizeof (DeviceUsage)); + + combined_rx_id = sysprof_capture_writer_request_counter (writer, 1); + combined_tx_id = sysprof_capture_writer_request_counter (writer, 1); + + g_strlcpy (ctr[0].category, "Network", sizeof ctr[0].category); + g_strlcpy (ctr[0].name, "RX Bytes", sizeof ctr[0].name); + g_strlcpy (ctr[0].description, "Combined", sizeof ctr[0].description); + ctr[0].id = combined_rx_id; + ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[0].value.v64 = 0; + + g_strlcpy (ctr[1].category, "Network", sizeof ctr[1].category); + g_strlcpy (ctr[1].name, "TX Bytes", sizeof ctr[1].name); + g_strlcpy (ctr[1].description, "Combined", sizeof ctr[1].description); + ctr[1].id = combined_tx_id; + ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[1].value.v64 = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctr, G_N_ELEMENTS (ctr)); + + n_read = dex_await_int64 (dex_aio_read (NULL, stat_fd, buf->data, buf->len, 0), &error); + if (n_read <= 0) + return dex_future_new_for_errno (errno); + + lineno = 0; + line_reader_init (&reader, (char *)buf->data, n_read); + while ((line = line_reader_next (&reader, &line_len))) + { + DeviceUsage dev = {0}; + g_autofree char *rx = NULL; + g_autofree char *tx = NULL; + char *ptr = line; + char *name; + + line[line_len] = 0; + + if (lineno++ < 2) + continue; + + for (; *ptr && g_ascii_isspace (*ptr); ptr++) { /* Do Nothing */ } + name = ptr; + for (; *ptr && *ptr != ':'; ptr++) { /* Do Nothing */ } + *ptr = 0; + + rx = g_strdup_printf ("RX Bytes (%s)", name); + tx = g_strdup_printf ("TX Bytes (%s)", name); + + g_strlcpy (ctr[0].category, "Network", sizeof ctr[0].category); + g_strlcpy (ctr[0].name, rx, sizeof ctr[0].name); + g_strlcpy (ctr[0].description, name, sizeof ctr[0].description); + ctr[0].id = sysprof_capture_writer_request_counter (writer, 1); + ctr[0].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[0].value.v64 = 0; + + g_strlcpy (ctr[1].category, "Network", sizeof ctr[1].category); + g_strlcpy (ctr[1].name, tx, sizeof ctr[1].name); + g_strlcpy (ctr[1].description, name, sizeof ctr[1].description); + ctr[1].id = sysprof_capture_writer_request_counter (writer, 1); + ctr[1].type = SYSPROF_CAPTURE_COUNTER_INT64; + ctr[1].value.v64 = 0; + + sysprof_capture_writer_define_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ctr, G_N_ELEMENTS (ctr)); + + dev.rx_bytes_id = ctr[0].id; + dev.tx_bytes_id = ctr[1].id; + g_strlcpy (dev.iface, name, sizeof dev.iface); + + g_array_append_val (devices, dev); + } + + values = g_new0 (SysprofCaptureCounterValue, (devices->len*2) + 2); + ids = g_new0 (guint, (devices->len*2) + 2); + ids[0] = combined_rx_id; + ids[1] = combined_tx_id; + for (guint i = 0; i < devices->len; i++) + { + const DeviceUsage *dev = &g_array_index (devices, DeviceUsage, i); + ids[(i+1)*2] = dev->rx_bytes_id; + ids[(i+1)*2+1] = dev->tx_bytes_id; + } + + for (;;) + { + DeviceUsage *first = &g_array_index (devices, DeviceUsage, 0); + gint64 combined_rx = 0; + gint64 combined_tx = 0; + + n_read = dex_await_int64 (dex_aio_read (NULL, stat_fd, buf->data, buf->len, 0), &error); + if (n_read <= 0) + break; + + lineno = 0; + line_reader_init (&reader, (char *)buf->data, n_read); + while ((line = line_reader_next (&reader, &line_len))) + { + DeviceUsage *dev; + guint index; + char *name; + char *ptr = line; + + line[line_len] = 0; + + if (lineno++ < 2) + continue; + + for (; *ptr && g_ascii_isspace (*ptr); ptr++) { /* Do Nothing */ } + name = ptr; + for (; *ptr && *ptr != ':'; ptr++) { /* Do Nothing */ } + *ptr = 0; + + if (!(dev = find_device_by_name (devices, name))) + continue; + + ptr++; + + sscanf (ptr, + "%"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT + " %"G_GINT64_FORMAT, + &dev->rx_bytes, + &dev->rx_packets, + &dev->rx_errors, + &dev->rx_dropped, + &dev->rx_fifo, + &dev->rx_frame, + &dev->rx_compressed, + &dev->rx_multicast, + &dev->tx_bytes, + &dev->tx_packets, + &dev->tx_errors, + &dev->tx_dropped, + &dev->tx_fifo, + &dev->tx_collisions, + &dev->tx_carrier, + &dev->tx_compressed); + + combined_rx += dev->rx_bytes; + combined_tx += dev->tx_bytes; + + index = dev - first; + + values[(index+1)*2].v64 = dev->rx_bytes; + values[(index+1)*2+1].v64 = dev->tx_bytes; + } + + values[0].v64 = combined_rx; + values[1].v64 = combined_tx; + + sysprof_capture_writer_set_counters (writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + ids, + values, + (devices->len + 1) * 2); + + /* Wait for cancellation or ½ second */ + dex_await (dex_future_first (dex_ref (record->cancellable), + dex_timeout_new_usec (G_USEC_PER_SEC / 2), + NULL), + NULL); + if (dex_future_get_status (record->cancellable) != DEX_FUTURE_STATUS_PENDING) + break; + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_network_usage_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + Record *record; + + g_assert (SYSPROF_IS_NETWORK_USAGE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_network_usage_record_fiber, + record, + record_free); +} + +static void +sysprof_network_usage_class_init (SysprofNetworkUsageClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_network_usage_record; +} + +static void +sysprof_network_usage_init (SysprofNetworkUsage *self) +{ +} + +SysprofInstrument * +sysprof_network_usage_new (void) +{ + return g_object_new (SYSPROF_TYPE_NETWORK_USAGE, NULL); +} diff --git a/src/libsysprof/sysprof-network-usage.h b/src/libsysprof/sysprof-network-usage.h new file mode 100644 index 00000000..5cd0cb5d --- /dev/null +++ b/src/libsysprof/sysprof-network-usage.h @@ -0,0 +1,42 @@ +/* sysprof-network-usage.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_NETWORK_USAGE (sysprof_network_usage_get_type()) +#define SYSPROF_IS_NETWORK_USAGE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_NETWORK_USAGE) +#define SYSPROF_NETWORK_USAGE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_NETWORK_USAGE, SysprofNetworkUsage) +#define SYSPROF_NETWORK_USAGE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_NETWORK_USAGE, SysprofNetworkUsageClass) + +typedef struct _SysprofNetworkUsage SysprofNetworkUsage; +typedef struct _SysprofNetworkUsageClass SysprofNetworkUsageClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_network_usage_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_network_usage_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofNetworkUsage, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-no-symbolizer.c b/src/libsysprof/sysprof-no-symbolizer.c new file mode 100644 index 00000000..0b0e5070 --- /dev/null +++ b/src/libsysprof/sysprof-no-symbolizer.c @@ -0,0 +1,77 @@ +/* sysprof-no-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-no-symbolizer.h" +#include "sysprof-symbolizer-private.h" + +struct _SysprofNoSymbolizer +{ + SysprofSymbolizer parent_instance; +}; + +struct _SysprofNoSymbolizerClass +{ + SysprofSymbolizerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofNoSymbolizer, sysprof_no_symbolizer, SYSPROF_TYPE_SYMBOLIZER) + +static SysprofSymbol * +sysprof_no_symbolizer_symbolize (SysprofSymbolizer *symbolizer, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + return NULL; +} + +static void +sysprof_no_symbolizer_class_init (SysprofNoSymbolizerClass *klass) +{ + SysprofSymbolizerClass *symbolizer_class = SYSPROF_SYMBOLIZER_CLASS (klass); + + symbolizer_class->symbolize = sysprof_no_symbolizer_symbolize; +} + +static void +sysprof_no_symbolizer_init (SysprofNoSymbolizer *self) +{ +} + +/** + * sysprof_no_symbolizer_get: + * + * Gets a #SysprofSymbolizer that will never symbolize. + * + * Returns: (transfer none): a #SysprofSymbolizer + */ +SysprofSymbolizer * +sysprof_no_symbolizer_get (void) +{ + static SysprofSymbolizer *instance; + + if (g_once_init_enter (&instance)) + g_once_init_leave (&instance, g_object_new (SYSPROF_TYPE_NO_SYMBOLIZER, NULL)); + + return instance; +} diff --git a/src/libsysprof/sysprof-no-symbolizer.h b/src/libsysprof/sysprof-no-symbolizer.h new file mode 100644 index 00000000..e958b670 --- /dev/null +++ b/src/libsysprof/sysprof-no-symbolizer.h @@ -0,0 +1,42 @@ +/* sysprof-no-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_NO_SYMBOLIZER (sysprof_no_symbolizer_get_type()) +#define SYSPROF_IS_NO_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_NO_SYMBOLIZER) +#define SYSPROF_NO_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_NO_SYMBOLIZER, SysprofNoSymbolizer) +#define SYSPROF_NO_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_NO_SYMBOLIZER, SysprofNoSymbolizerClass) + +typedef struct _SysprofNoSymbolizer SysprofNoSymbolizer; +typedef struct _SysprofNoSymbolizerClass SysprofNoSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_no_symbolizer_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolizer *sysprof_no_symbolizer_get (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofNoSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-path-resolver.c b/src/libsysprof/sysprof-path-resolver.c deleted file mode 100644 index 7d7d9bee..00000000 --- a/src/libsysprof/sysprof-path-resolver.c +++ /dev/null @@ -1,554 +0,0 @@ -/* sysprof-path-resolver.c - * - * Copyright 2021 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "sysprof-path-resolver.h" - -struct _SysprofPathResolver -{ - GArray *mounts; - GArray *mountpoints; -}; - -typedef struct -{ - /* The path on the host system */ - char *on_host; - - /* The path inside the process domain */ - char *in_process; - - /* The length of @in_process in bytes */ - guint in_process_len; - - /* The depth of the mount (for FUSE overlays) */ - int depth; -} Mountpoint; - -typedef struct -{ - char *device; - char *mountpoint; - char *filesystem; - char *subvolid; - char *subvol; -} Mount; - -typedef struct _st_mountinfo -{ - char *id; - char *parent_id; - char *st_dev; - char *root; - char *mount_point; - char *mount_options; - char *filesystem; - char *mount_source; - char *super_options; -} st_mountinfo; - -static void -clear_st_mountinfo (st_mountinfo *st) -{ - g_free (st->id); - g_free (st->parent_id); - g_free (st->st_dev); - g_free (st->root); - g_free (st->mount_point); - g_free (st->mount_options); - g_free (st->filesystem); - g_free (st->mount_source); - g_free (st->super_options); -} - -G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (st_mountinfo, clear_st_mountinfo) - -static void -clear_mount (Mount *m) -{ - g_clear_pointer (&m->device, g_free); - g_clear_pointer (&m->mountpoint, g_free); - g_clear_pointer (&m->subvolid, g_free); - g_clear_pointer (&m->subvol, g_free); - g_clear_pointer (&m->filesystem, g_free); -} - -static void -clear_mountpoint (Mountpoint *mp) -{ - g_clear_pointer (&mp->on_host, g_free); - g_clear_pointer (&mp->in_process, g_free); -} - -static gboolean -ignore_fs (const char *fs) -{ - static gsize initialized; - static GHashTable *ignored; - - if (g_once_init_enter (&initialized)) - { - ignored = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_add (ignored, (char *)"autofs"); - g_hash_table_add (ignored, (char *)"binfmt_misc"); - g_hash_table_add (ignored, (char *)"bpf"); - g_hash_table_add (ignored, (char *)"cgroup"); - g_hash_table_add (ignored, (char *)"cgroup2"); - g_hash_table_add (ignored, (char *)"configfs"); - g_hash_table_add (ignored, (char *)"debugfs"); - g_hash_table_add (ignored, (char *)"devpts"); - g_hash_table_add (ignored, (char *)"devtmpfs"); - g_hash_table_add (ignored, (char *)"efivarfs"); - g_hash_table_add (ignored, (char *)"fusectl"); - g_hash_table_add (ignored, (char *)"hugetlbfs"); - g_hash_table_add (ignored, (char *)"mqueue"); - g_hash_table_add (ignored, (char *)"none"); - g_hash_table_add (ignored, (char *)"portal"); - g_hash_table_add (ignored, (char *)"proc"); - g_hash_table_add (ignored, (char *)"pstore"); - g_hash_table_add (ignored, (char *)"ramfs"); - g_hash_table_add (ignored, (char *)"rpc_pipefs"); - g_hash_table_add (ignored, (char *)"securityfs"); - g_hash_table_add (ignored, (char *)"selinuxfs"); - g_hash_table_add (ignored, (char *)"sunrpc"); - g_hash_table_add (ignored, (char *)"sysfs"); - g_hash_table_add (ignored, (char *)"systemd-1"); - g_hash_table_add (ignored, (char *)"tmpfs"); - g_hash_table_add (ignored, (char *)"tracefs"); - g_once_init_leave (&initialized, (gsize)1); - } - - if (g_str_has_prefix (fs, "fuse.")) - return TRUE; - - return g_hash_table_contains (ignored, fs); -} - -static char * -path_copy_with_trailing_slash (const char *path) -{ - if (g_str_has_suffix (path, "/")) - return g_strdup (path); - else - return g_strdup_printf ("%s/", path); -} - -static void -decode_space (char **str) -{ - /* Replace encoded space "\040" with ' ' */ - if (strstr (*str, "\\040") != NULL) - { - g_auto(GStrv) parts = g_strsplit (*str, "\\040", 0); - g_free (*str); - *str = g_strjoinv (" ", parts); - } -} - -static char * -strdup_decode_space (const char *str) -{ - char *copy = g_strdup (str); - decode_space (©); - return copy; -} - -static gboolean -has_prefix_or_equal (const char *str, - const char *prefix) -{ - return g_str_has_prefix (str, prefix) || strcmp (str, prefix) == 0; -} - -static char * -get_option (const char *options, - const char *option) -{ - g_auto(GStrv) parts = NULL; - - g_assert (option != NULL); - g_assert (g_str_has_suffix (option, "=")); - - if (options == NULL) - return NULL; - - parts = g_strsplit (options, ",", 0); - - for (guint i = 0; parts[i] != NULL; i++) - { - if (g_str_has_prefix (parts[i], option)) - { - const char *ret = parts[i] + strlen (option); - - /* Easier to handle "" as NULL */ - if (*ret == 0) - return NULL; - - return g_strdup (ret); - } - } - - return NULL; -} - -static gboolean -parse_st_mountinfo (st_mountinfo *mi, - const char *line) -{ - g_auto(GStrv) parts = NULL; - guint i; - - g_assert (mi != NULL); - g_assert (line != NULL); - - memset (mi, 0, sizeof *mi); - - parts = g_strsplit (line, " ", 0); - if (g_strv_length (parts) < 10) - return FALSE; - - mi->id = g_strdup (parts[0]); - mi->parent_id = g_strdup (parts[1]); - mi->st_dev = g_strdup (parts[2]); - mi->root = strdup_decode_space (parts[3]); - mi->mount_point = strdup_decode_space (parts[4]); - mi->mount_options = strdup_decode_space (parts[5]); - - for (i = 6; parts[i] != NULL && !g_str_equal (parts[i], "-"); i++) - { - /* Do nothing. We just want to skip until after the optional tags - * section which is finished with " - ". - */ - } - - /* Skip past - if there is anything */ - if (parts[i] == NULL || parts[++i] == NULL) - return TRUE; - - /* Get filesystem if provided */ - mi->filesystem = g_strdup (parts[i++]); - if (parts[i] == NULL) - return TRUE; - - /* Get mount source if provided */ - mi->mount_source = strdup_decode_space (parts[i++]); - if (parts[i] == NULL) - return TRUE; - - /* Get super options if provided */ - mi->super_options = strdup_decode_space (parts[i++]); - if (parts[i] == NULL) - return TRUE; - - /* Perhaps mountinfo will be extended once again ... */ - - return TRUE; -} - -static void -parse_mounts (SysprofPathResolver *self, - const char *mounts) -{ - g_auto(GStrv) lines = NULL; - - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (mounts != NULL); - - lines = g_strsplit (mounts, "\n", 0); - - for (guint i = 0; lines[i]; i++) - { - g_auto(GStrv) parts = g_strsplit (lines[i], " ", 5); - g_autofree char *subvolid = NULL; - g_autofree char *subvol = NULL; - const char *filesystem; - const char *mountpoint; - const char *device; - const char *options; - Mount m; - - /* Field 0: device - * Field 1: mountpoint - * Field 2: filesystem - * Field 3: Options - * .. Ignored .. - */ - if (g_strv_length (parts) != 5) - continue; - - filesystem = parts[2]; - if (ignore_fs (filesystem)) - continue; - - for (guint j = 0; parts[j]; j++) - decode_space (&parts[j]); - - device = parts[0]; - mountpoint = parts[1]; - options = parts[3]; - - - if (g_strcmp0 (filesystem, "btrfs") == 0) - { - subvolid = get_option (options, "subvolid="); - subvol = get_option (options, "subvol="); - } - - m.device = g_strdup (device); - m.filesystem = g_strdup (filesystem); - m.mountpoint = path_copy_with_trailing_slash (mountpoint); - m.subvolid = g_steal_pointer (&subvolid); - m.subvol = g_steal_pointer (&subvol); - - g_array_append_val (self->mounts, m); - } -} - -static const Mount * -find_mount (SysprofPathResolver *self, - const st_mountinfo *mi) -{ - g_autofree char *subvolid = NULL; - - g_assert (self != NULL); - g_assert (mi != NULL); - - subvolid = get_option (mi->super_options, "subvolid="); - - for (guint i = 0; i < self->mounts->len; i++) - { - const Mount *mount = &g_array_index (self->mounts, Mount, i); - - if (g_strcmp0 (mount->device, mi->mount_source) == 0) - { - /* Sanity check that filesystems match */ - if (g_strcmp0 (mount->filesystem, mi->filesystem) != 0) - continue; - - /* If we have a subvolume (btrfs) make sure they match */ - if (subvolid == NULL || - g_strcmp0 (subvolid, mount->subvolid) == 0) - return mount; - } - } - - return NULL; -} - -static void -parse_mountinfo_line (SysprofPathResolver *self, - const char *line) -{ - g_auto(st_mountinfo) st_mi = {0}; - g_autofree char *subvol = NULL; - const char *path; - const Mount *mount; - Mountpoint mp = {0}; - - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (self->mountpoints != NULL); - - if (!parse_st_mountinfo (&st_mi, line)) - return; - - if (ignore_fs (st_mi.filesystem)) - return; - - if (!(mount = find_mount (self, &st_mi))) - return; - - subvol = get_option (st_mi.super_options, "subvol="); - path = st_mi.root; - - /* If the mount root has a prefix of the subvolume, then - * subtract that from the path (as we will resolve relative - * to the location where it is mounted via the Mount.mountpoint. - */ - if (subvol != NULL && has_prefix_or_equal (path, subvol)) - { - path += strlen (subvol); - if (*path == 0) - path = NULL; - } - - if (path == NULL) - { - mp.on_host = g_strdup (mount->mountpoint); - } - else - { - while (*path == '/') - path++; - mp.on_host = g_build_filename (mount->mountpoint, path, NULL); - } - - if (g_str_has_suffix (mp.on_host, "/") && - !g_str_has_suffix (st_mi.mount_point, "/")) - mp.in_process = g_build_filename (st_mi.mount_point, "/", NULL); - else - mp.in_process = g_strdup (st_mi.mount_point); - - mp.in_process_len = strlen (mp.in_process); - mp.depth = -1; - - g_array_append_val (self->mountpoints, mp); -} - -static gint -sort_by_length (gconstpointer a, - gconstpointer b) -{ - const Mountpoint *mpa = a; - const Mountpoint *mpb = b; - gsize alen = strlen (mpa->in_process); - gsize blen = strlen (mpb->in_process); - - if (alen > blen) - return -1; - else if (blen > alen) - return 1; - - if (mpa->depth < mpb->depth) - return -1; - else if (mpa->depth > mpb->depth) - return 1; - - return 0; -} - -static void -parse_mountinfo (SysprofPathResolver *self, - const char *mountinfo) -{ - g_auto(GStrv) lines = NULL; - - g_assert (self != NULL); - g_assert (self->mounts != NULL); - g_assert (self->mountpoints != NULL); - g_assert (mountinfo != NULL); - - lines = g_strsplit (mountinfo, "\n", 0); - - for (guint i = 0; lines[i]; i++) - parse_mountinfo_line (self, lines[i]); - - g_array_sort (self->mountpoints, sort_by_length); -} - -SysprofPathResolver * -_sysprof_path_resolver_new (const char *mounts, - const char *mountinfo) -{ - SysprofPathResolver *self; - - self = g_slice_new0 (SysprofPathResolver); - - self->mounts = g_array_new (FALSE, FALSE, sizeof (Mount)); - self->mountpoints = g_array_new (FALSE, FALSE, sizeof (Mountpoint)); - - g_array_set_clear_func (self->mounts, (GDestroyNotify)clear_mount); - g_array_set_clear_func (self->mountpoints, (GDestroyNotify)clear_mountpoint); - - if (mounts != NULL) - parse_mounts (self, mounts); - - if (mountinfo != NULL) - parse_mountinfo (self, mountinfo); - - return self; -} - -void -_sysprof_path_resolver_free (SysprofPathResolver *self) -{ - g_clear_pointer (&self->mountpoints, g_array_unref); - g_clear_pointer (&self->mounts, g_array_unref); - g_slice_free (SysprofPathResolver, self); -} - -void -_sysprof_path_resolver_add_overlay (SysprofPathResolver *self, - const char *in_process, - const char *on_host, - int depth) -{ - Mountpoint mp; - - g_return_if_fail (self != NULL); - g_return_if_fail (in_process != NULL); - g_return_if_fail (on_host != NULL); - - mp.in_process = path_copy_with_trailing_slash (in_process); - mp.in_process_len = strlen (mp.in_process); - mp.on_host = path_copy_with_trailing_slash (on_host); - mp.depth = depth; - - g_array_append_val (self->mountpoints, mp); - g_array_sort (self->mountpoints, sort_by_length); -} - -char * -_sysprof_path_resolver_resolve (SysprofPathResolver *self, - const char *path) -{ - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (path != NULL, NULL); - - /* TODO: Cache the directory name of @path and use that for followup - * searches like we did in SysprofMountinfo. - */ - - for (guint i = 0; i < self->mountpoints->len; i++) - { - const Mountpoint *mp = &g_array_index (self->mountpoints, Mountpoint, i); - - if (g_str_has_prefix (path, mp->in_process)) - { - g_autofree char *dst = g_build_filename (mp->on_host, - path + mp->in_process_len, - NULL); - - /* If the depth is > -1, then we are dealing with an overlay. We - * unfortunately have to stat() to see if the file exists and then - * skip over this if not. - * - * TODO: This is going to break when we are recording from within - * flatpak as we'll not be able to stat files unless they are - * within the current users home, so system containers would - * be unlilkely to resolve. - */ - if (mp->depth > -1) - { - if (g_file_test (dst, G_FILE_TEST_EXISTS)) - return g_steal_pointer (&dst); - continue; - } - - return g_steal_pointer (&dst); - } - } - - return NULL; -} diff --git a/src/libsysprof/sysprof-path-resolver.h b/src/libsysprof/sysprof-path-resolver.h deleted file mode 100644 index 5267cf96..00000000 --- a/src/libsysprof/sysprof-path-resolver.h +++ /dev/null @@ -1,41 +0,0 @@ -/* sysprof-path-resolver.h - * - * Copyright 2021 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -G_BEGIN_DECLS - -typedef struct _SysprofPathResolver SysprofPathResolver; - -SysprofPathResolver *_sysprof_path_resolver_new (const char *mounts, - const char *mountinfo); -void _sysprof_path_resolver_add_overlay (SysprofPathResolver *self, - const char *in_process, - const char *on_host, - int depth); -void _sysprof_path_resolver_free (SysprofPathResolver *self); -char *_sysprof_path_resolver_resolve (SysprofPathResolver *self, - const char *path); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofPathResolver, _sysprof_path_resolver_free) - -G_END_DECLS diff --git a/src/libsysprof/sysprof-perf-counter.c b/src/libsysprof/sysprof-perf-counter.c deleted file mode 100644 index 2d0b9f46..00000000 --- a/src/libsysprof/sysprof-perf-counter.c +++ /dev/null @@ -1,504 +0,0 @@ -/* sysprof-perf-counter.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include -#include -#include -#ifdef HAVE_STDATOMIC_H -# include -#endif -#include -#include -#include -#include -#include - -#include "sysprof-helpers.h" -#include "sysprof-perf-counter.h" - -/* - * Number of pages to map for the ring buffer. We map one additional buffer - * at the beginning for header information to communicate with perf. - */ -#define N_PAGES 32 - -/* - * This represents a stream coming to us from perf. All SysprofPerfCounterInfo - * share a single GSource used for watching incoming G_IO_IN requests. - * The map is the mmap() zone we are using as a ring buffer for communicating - * with perf. The rest is for managing the ring buffer. - */ -typedef struct -{ - gint fd; - gpointer fdtag; - struct perf_event_mmap_page *map; - guint8 *data; - guint64 tail; - gint cpu; - guint in_callback : 1; -} SysprofPerfCounterInfo; - -struct _SysprofPerfCounter -{ - volatile gint ref_count; - - /* - * If we are should currently be enabled. We allow calling - * multiple times and disabling when we reach zero. - */ - guint enabled; - - /* - * Our main context and source for delivering callbacks. - */ - GMainContext *context; - GSource *source; - - /* - * An array of SysprofPerfCounterInfo, indicating all of our open - * perf stream.s - */ - GPtrArray *info; - - /* - * The callback to execute for every discovered perf event. - */ - SysprofPerfCounterCallback callback; - gpointer callback_data; - GDestroyNotify callback_data_destroy; - - /* - * The number of samples we've recorded. - */ - guint64 n_samples; -}; - -typedef struct -{ - GSource source; - SysprofPerfCounter *counter; -} PerfGSource; - -G_DEFINE_BOXED_TYPE (SysprofPerfCounter, - sysprof_perf_counter, - (GBoxedCopyFunc)sysprof_perf_counter_ref, - (GBoxedFreeFunc)sysprof_perf_counter_unref) - -static gboolean -perf_gsource_dispatch (GSource *source, - GSourceFunc callback, - gpointer user_data) -{ - return callback ? callback (user_data) : G_SOURCE_CONTINUE; -} - -static GSourceFuncs source_funcs = { - NULL, NULL, perf_gsource_dispatch, NULL -}; - -static void -sysprof_perf_counter_info_free (SysprofPerfCounterInfo *info) -{ - if (info->map) - { - gsize map_size; - - map_size = N_PAGES * getpagesize () + getpagesize (); - munmap (info->map, map_size); - - info->map = NULL; - info->data = NULL; - } - - if (info->fd != -1) - { - close (info->fd); - info->fd = 0; - } - - g_slice_free (SysprofPerfCounterInfo, info); -} - -static void -sysprof_perf_counter_finalize (SysprofPerfCounter *self) -{ - guint i; - - g_assert (self != NULL); - g_assert (self->ref_count == 0); - - for (i = 0; i < self->info->len; i++) - { - SysprofPerfCounterInfo *info = g_ptr_array_index (self->info, i); - - if (info->fdtag) - g_source_remove_unix_fd (self->source, info->fdtag); - - sysprof_perf_counter_info_free (info); - } - - if (self->callback_data_destroy) - self->callback_data_destroy (self->callback_data); - - g_clear_pointer (&self->source, g_source_destroy); - g_clear_pointer (&self->info, g_ptr_array_unref); - g_clear_pointer (&self->context, g_main_context_unref); - g_slice_free (SysprofPerfCounter, self); -} - -void -sysprof_perf_counter_unref (SysprofPerfCounter *self) -{ - g_return_if_fail (self != NULL); - g_return_if_fail (self->ref_count > 0); - - if (g_atomic_int_dec_and_test (&self->ref_count)) - sysprof_perf_counter_finalize (self); -} - -SysprofPerfCounter * -sysprof_perf_counter_ref (SysprofPerfCounter *self) -{ - g_return_val_if_fail (self != NULL, NULL); - g_return_val_if_fail (self->ref_count > 0, NULL); - - g_atomic_int_inc (&self->ref_count); - - return self; -} - -static void -sysprof_perf_counter_flush (SysprofPerfCounter *self, - SysprofPerfCounterInfo *info) -{ - guint64 head; - guint64 tail; - guint64 n_bytes = N_PAGES * getpagesize (); - guint64 mask = n_bytes - 1; - - g_assert (self != NULL); - g_assert (info != NULL); - - tail = info->tail; - head = info->map->data_head; - -#ifdef HAVE_STDATOMIC_H - atomic_thread_fence (memory_order_acquire); -#elif G_GNUC_CHECK_VERSION(3, 0) - __sync_synchronize (); -#endif - - if (head < tail) - tail = head; - - while ((head - tail) >= sizeof (struct perf_event_header)) - { - g_autofree guint8 *free_me = NULL; - struct perf_event_header *header; - guint8 buffer[4096]; - - /* Note that: - * - * - perf events are a multiple of 64 bits - * - the perf event header is 64 bits - * - the data area is a multiple of 64 bits - * - * which means there will always be space for one header, which means we - * can safely dereference the size field. - */ - header = (struct perf_event_header *)(gpointer)(info->data + (tail & mask)); - - if (header->size > head - tail) - { - /* The kernel did not generate a complete event. - * I don't think that can happen, but we may as well - * be paranoid. - */ - break; - } - - if (info->data + (tail & mask) + header->size > info->data + n_bytes) - { - gint n_before; - gint n_after; - guint8 *b; - - if (header->size > sizeof buffer) - free_me = b = g_malloc (header->size); - else - b = buffer; - - n_after = (tail & mask) + header->size - n_bytes; - n_before = header->size - n_after; - - memcpy (b, info->data + (tail & mask), n_before); - memcpy (b + n_before, info->data, n_after); - - header = (struct perf_event_header *)(gpointer)b; - } - - if (header->type == PERF_RECORD_SAMPLE) - self->n_samples++; - - if (self->callback != NULL) - { - info->in_callback = TRUE; - self->callback ((SysprofPerfCounterEvent *)header, info->cpu, self->callback_data); - info->in_callback = FALSE; - } - - tail += header->size; - } - - info->tail = tail; - -#ifdef HAVE_STDATOMIC_H - atomic_thread_fence (memory_order_seq_cst); -#elif G_GNUC_CHECK_VERSION(3, 0) - __sync_synchronize (); -#endif - - info->map->data_tail = tail; -} - -static gboolean -sysprof_perf_counter_dispatch (gpointer user_data) -{ - SysprofPerfCounter *self = user_data; - - g_assert (self != NULL); - g_assert (self->info != NULL); - - for (guint i = 0; i < self->info->len; i++) - { - SysprofPerfCounterInfo *info = g_ptr_array_index (self->info, i); - - sysprof_perf_counter_flush (self, info); - } - - return G_SOURCE_CONTINUE; -} - -static void -sysprof_perf_counter_enable_info (SysprofPerfCounter *self, - SysprofPerfCounterInfo *info) -{ - g_assert (self != NULL); - g_assert (info != NULL); - - if (0 != ioctl (info->fd, PERF_EVENT_IOC_ENABLE)) - g_warning ("Failed to enable counters"); - - g_source_modify_unix_fd (self->source, info->fdtag, G_IO_IN); -} - -SysprofPerfCounter * -sysprof_perf_counter_new (GMainContext *context) -{ - SysprofPerfCounter *ret; - - if (context == NULL) - context = g_main_context_default (); - - ret = g_slice_new0 (SysprofPerfCounter); - ret->ref_count = 1; - ret->info = g_ptr_array_new (); - ret->context = g_main_context_ref (context); - ret->source = g_source_new (&source_funcs, sizeof (PerfGSource)); - - ((PerfGSource *)ret->source)->counter = ret; - g_source_set_callback (ret->source, sysprof_perf_counter_dispatch, ret, NULL); - g_source_set_name (ret->source, "[perf]"); - g_source_attach (ret->source, context); - - return ret; -} - -void -sysprof_perf_counter_close (SysprofPerfCounter *self, - gint fd) -{ - guint i; - - g_return_if_fail (self != NULL); - g_return_if_fail (fd != -1); - - for (i = 0; i < self->info->len; i++) - { - SysprofPerfCounterInfo *info = g_ptr_array_index (self->info, i); - - if (info->fd == fd) - { - g_ptr_array_remove_index (self->info, i); - if (self->source) - g_source_remove_unix_fd (self->source, info->fdtag); - sysprof_perf_counter_info_free (info); - return; - } - } -} - -static void -sysprof_perf_counter_add_info (SysprofPerfCounter *self, - int fd, - int cpu) -{ - SysprofPerfCounterInfo *info; - guint8 *map; - gsize map_size; - - g_assert (self != NULL); - g_assert (fd != -1); - - map_size = N_PAGES * getpagesize () + getpagesize (); - map = mmap (NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - if (map == MAP_FAILED) - { - close (fd); - return; - } - - info = g_slice_new0 (SysprofPerfCounterInfo); - info->fd = fd; - info->map = (gpointer)map; - info->data = map + getpagesize (); - info->tail = 0; - info->cpu = cpu; - - g_ptr_array_add (self->info, info); - - info->fdtag = g_source_add_unix_fd (self->source, info->fd, G_IO_ERR); - - if (self->enabled) - sysprof_perf_counter_enable_info (self, info); -} - -void -sysprof_perf_counter_take_fd (SysprofPerfCounter *self, - int fd) -{ - g_return_if_fail (self != NULL); - g_return_if_fail (fd > -1); - - sysprof_perf_counter_add_info (self, fd, -1); -} - -gint -sysprof_perf_counter_open (SysprofPerfCounter *self, - struct perf_event_attr *attr, - GPid pid, - gint cpu, - gint group_fd, - gulong flags) -{ - SysprofHelpers *helpers = sysprof_helpers_get_default (); - gint out_fd = -1; - - g_return_val_if_fail (self != NULL, -1); - g_return_val_if_fail (attr != NULL, -1); - g_return_val_if_fail (cpu >= -1, -1); - g_return_val_if_fail (pid >= -1, -1); - g_return_val_if_fail (group_fd >= -1, -1); - - if (sysprof_helpers_perf_event_open (helpers, attr, pid, cpu, group_fd, flags, NULL, &out_fd, NULL)) - { - sysprof_perf_counter_take_fd (self, out_fd); - return out_fd; - } - - return -1; -} - -void -sysprof_perf_counter_set_callback (SysprofPerfCounter *self, - SysprofPerfCounterCallback callback, - gpointer callback_data, - GDestroyNotify callback_data_destroy) -{ - g_return_if_fail (self != NULL); - - if (self->callback_data_destroy) - self->callback_data_destroy (self->callback_data); - - self->callback = callback; - self->callback_data = callback_data; - self->callback_data_destroy = callback_data_destroy; -} - -void -sysprof_perf_counter_enable (SysprofPerfCounter *self) -{ - g_return_if_fail (self != NULL); - - if (g_atomic_int_add (&self->enabled, 1) == 0) - { - for (guint i = 0; i < self->info->len; i++) - { - SysprofPerfCounterInfo *info = g_ptr_array_index (self->info, i); - - sysprof_perf_counter_enable_info (self, info); - } - } -} - -void -sysprof_perf_counter_disable (SysprofPerfCounter *self) -{ - g_return_if_fail (self != NULL); - - if (g_atomic_int_dec_and_test (&self->enabled)) - { - for (guint i = 0; i < self->info->len; i++) - { - SysprofPerfCounterInfo *info = g_ptr_array_index (self->info, i); - - if (0 != ioctl (info->fd, PERF_EVENT_IOC_DISABLE)) - g_warning ("Failed to disable counters"); - - if (!info->in_callback) - sysprof_perf_counter_flush (self, info); - - g_source_modify_unix_fd (self->source, info->fdtag, G_IO_ERR); - } - } -} diff --git a/src/libsysprof/sysprof-perf-counter.h b/src/libsysprof/sysprof-perf-counter.h deleted file mode 100644 index 410e198c..00000000 --- a/src/libsysprof/sysprof-perf-counter.h +++ /dev/null @@ -1,151 +0,0 @@ -/* sysprof-perf-counter.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include -#include - - -G_BEGIN_DECLS - -/* Structs representing the layouts of perf records returned by the - * kernel. - * - * perf returns variable-layout structs based on the - * perf_event_sample_format selectors in perf_event_attr.sample_type. - * These structs are the particular layouts that sysprof requests. - */ - -#define SYSPROF_TYPE_PERF_COUNTER (sysprof_perf_counter_get_type()) - -typedef struct _SysprofPerfCounter SysprofPerfCounter; - -#pragma pack(push, 1) - -typedef struct -{ - struct perf_event_header header; - guint32 pid; - guint32 ppid; - guint32 tid; - guint32 ptid; - guint64 time; -} SysprofPerfCounterEventFork; - -typedef struct -{ - struct perf_event_header header; - guint32 pid; - guint32 tid; - gchar comm[0]; -} SysprofPerfCounterEventComm; - -typedef struct -{ - struct perf_event_header header; - guint32 pid; - guint32 ppid; - guint32 tid; - guint32 ptid; - guint64 time; -} SysprofPerfCounterEventExit; - -typedef struct -{ - struct perf_event_header header; - guint32 pid; - guint32 tid; - guint64 addr; - guint64 len; - guint64 pgoff; - char filename[0]; -} SysprofPerfCounterEventMmap; - -typedef struct -{ - struct perf_event_header header; - guint64 identifier; - guint64 ip; - guint32 pid; - guint32 tid; - guint64 time; - guint64 n_ips; - guint64 ips[0]; -} SysprofPerfCounterEventCallchain; - -typedef struct -{ - struct perf_event_header header; - guint64 identifier; - guint64 ip; - guint32 pid; - guint32 tid; - guint64 time; - guint32 raw_size; - guchar raw[]; -} SysprofPerfCounterEventTracepoint; - -typedef union -{ - struct perf_event_header header; - guint8 raw[0]; - SysprofPerfCounterEventFork fork; - SysprofPerfCounterEventComm comm; - SysprofPerfCounterEventExit exit; - SysprofPerfCounterEventMmap mmap; - SysprofPerfCounterEventCallchain callchain; - SysprofPerfCounterEventTracepoint tracepoint; -} SysprofPerfCounterEvent; - -#pragma pack(pop) - -typedef void (*SysprofPerfCounterCallback) (SysprofPerfCounterEvent *event, - guint cpu, - gpointer user_data); - -GType sysprof_perf_counter_get_type (void); -SysprofPerfCounter *sysprof_perf_counter_new (GMainContext *context); -void sysprof_perf_counter_set_callback (SysprofPerfCounter *self, - SysprofPerfCounterCallback callback, - gpointer callback_data, - GDestroyNotify callback_data_destroy); -void sysprof_perf_counter_add_pid (SysprofPerfCounter *self, - GPid pid); -gint sysprof_perf_counter_open (SysprofPerfCounter *self, - struct perf_event_attr *attr, - GPid pid, - gint cpu, - gint group_fd, - gulong flags); -void sysprof_perf_counter_take_fd (SysprofPerfCounter *self, - int fd); -void sysprof_perf_counter_enable (SysprofPerfCounter *self); -void sysprof_perf_counter_disable (SysprofPerfCounter *self); -void sysprof_perf_counter_close (SysprofPerfCounter *self, - gint fd); -SysprofPerfCounter *sysprof_perf_counter_ref (SysprofPerfCounter *self); -void sysprof_perf_counter_unref (SysprofPerfCounter *self); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-perf-event-stream-private.h b/src/libsysprof/sysprof-perf-event-stream-private.h new file mode 100644 index 00000000..aab37632 --- /dev/null +++ b/src/libsysprof/sysprof-perf-event-stream-private.h @@ -0,0 +1,141 @@ +/* sysprof-perf-event-stream-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPerfEventFork +{ + struct perf_event_header header; + guint32 pid; + guint32 ppid; + guint32 tid; + guint32 ptid; + guint64 time; +} SysprofPerfEventFork +SYSPROF_ALIGNED_END(1); + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPerfEventComm +{ + struct perf_event_header header; + guint32 pid; + guint32 tid; + gchar comm[0]; +} SysprofPerfEventComm +SYSPROF_ALIGNED_END(1); + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPerfEventExit +{ + struct perf_event_header header; + guint32 pid; + guint32 ppid; + guint32 tid; + guint32 ptid; + guint64 time; +} SysprofPerfEventExit +SYSPROF_ALIGNED_END(1); + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPerfEventMmap +{ + struct perf_event_header header; + guint32 pid; + guint32 tid; + guint64 addr; + guint64 len; + guint64 pgoff; + char filename[0]; +} SysprofPerfEventMmap +SYSPROF_ALIGNED_END(1); + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPerfEventCallchain +{ + struct perf_event_header header; + guint64 identifier; + guint64 ip; + guint32 pid; + guint32 tid; + guint64 time; + guint64 n_ips; + guint64 ips[0]; +} SysprofPerfEventCallchain +SYSPROF_ALIGNED_END(1); + +SYSPROF_ALIGNED_BEGIN(1) +typedef struct _SysprofPerfEventTracepoint +{ + struct perf_event_header header; + guint64 identifier; + guint64 ip; + guint32 pid; + guint32 tid; + guint64 time; + guint32 raw_size; + guchar raw[]; +} SysprofPerfEventTracepoint +SYSPROF_ALIGNED_END(1); + +SYSPROF_ALIGNED_BEGIN(1) +typedef union _SysprofPerfEvent +{ + struct perf_event_header header; + SysprofPerfEventFork fork; + SysprofPerfEventComm comm; + SysprofPerfEventExit exit; + SysprofPerfEventMmap mmap; + SysprofPerfEventCallchain callchain; + SysprofPerfEventTracepoint tracepoint; + guint8 raw[0]; +} SysprofPerfEvent +SYSPROF_ALIGNED_END(1); + +typedef void (*SysprofPerfEventCallback) (const SysprofPerfEvent *event, + guint cpu, + gpointer user_data); + +#define SYSPROF_TYPE_PERF_EVENT_STREAM (sysprof_perf_event_stream_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofPerfEventStream, sysprof_perf_event_stream, SYSPROF, PERF_EVENT_STREAM, GObject) + +DexFuture *sysprof_perf_event_stream_new (GDBusConnection *connection, + struct perf_event_attr *attr, + int cpu, + int group_fd, + gulong flags, + SysprofPerfEventCallback callback, + gpointer callback_data, + GDestroyNotify callback_data_destroy); +gboolean sysprof_perf_event_stream_enable (SysprofPerfEventStream *self, + GError **error); +gboolean sysprof_perf_event_stream_disable (SysprofPerfEventStream *self, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-perf-event-stream.c b/src/libsysprof/sysprof-perf-event-stream.c new file mode 100644 index 00000000..4b5be46e --- /dev/null +++ b/src/libsysprof/sysprof-perf-event-stream.c @@ -0,0 +1,577 @@ +/* sysprof-perf-event-stream.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* Sysprof -- Sampling, systemwide CPU profiler + * Copyright 2004, Red Hat, Inc. + * Copyright 2004, 2005, Soeren Sandmann + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_STDATOMIC_H +# include +#endif +#include +#include +#include +#include +#include + +#include "sysprof-perf-event-stream-private.h" + +/* + * Number of pages to map for the ring buffer. We map one additional buffer + * at the beginning for header information to communicate with perf. + */ +#define N_PAGES 32 + +struct _SysprofPerfEventStream +{ + GObject parent_instance; + + GDBusConnection *connection; + + GSource *source; + + struct perf_event_attr attr; + + int self_pid; + int cpu; + int group_fd; + gulong flags; + + SysprofPerfEventCallback callback; + gpointer callback_data; + GDestroyNotify callback_data_destroy; + + DexPromise *promise; + + int perf_fd; + + struct perf_event_mmap_page *map; + guint8 *map_data; + guint64 tail; + + guint active : 1; +}; + +typedef struct _SysprofPerfEventSource +{ + GSource source; + SysprofPerfEventStream *stream; + gint64 next_ready_time; + int timeout_msec; +} SysprofPerfEventSource; + +enum { + PROP_0, + PROP_ACTIVE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofPerfEventStream, sysprof_perf_event_stream, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static GVariant * +build_options_dict (const struct perf_event_attr *attr) +{ + return g_variant_take_ref ( + g_variant_new_parsed ("[" + "{'comm', <%b>}," +#ifdef HAVE_PERF_CLOCKID + "{'clockid', <%i>}," + "{'use_clockid', <%b>}," +#endif + "{'config', <%t>}," + "{'disabled', <%b>}," + "{'exclude_idle', <%b>}," + "{'mmap', <%b>}," + "{'wakeup_events', <%u>}," + "{'sample_id_all', <%b>}," + "{'sample_period', <%t>}," + "{'sample_type', <%t>}," + "{'task', <%b>}," + "{'type', <%u>}" + "]", + (gboolean)!!attr->comm, +#ifdef HAVE_PERF_CLOCKID + (gint32)attr->clockid, + (gboolean)!!attr->use_clockid, +#endif + (guint64)attr->config, + (gboolean)!!attr->disabled, + (gboolean)!!attr->exclude_idle, + (gboolean)!!attr->mmap, + (guint32)attr->wakeup_events, + (gboolean)!!attr->sample_id_all, + (guint64)attr->sample_period, + (guint64)attr->sample_type, + (gboolean)!!attr->task, + (guint32)attr->type)); +} + +static void +sysprof_perf_event_stream_flush (SysprofPerfEventStream *self) +{ + SysprofPerfEventSource *source = (SysprofPerfEventSource *)self->source; + guint64 n_bytes = N_PAGES * sysprof_getpagesize (); + guint64 mask = n_bytes - 1; + guint64 head; + guint64 tail; + gboolean lost_records = FALSE; + guint us = 0; + guint them = 0; + + g_assert (SYSPROF_IS_PERF_EVENT_STREAM (self)); + + tail = self->tail; + head = self->map->data_head; + +#ifdef HAVE_STDATOMIC_H + atomic_thread_fence (memory_order_acquire); +#elif G_GNUC_CHECK_VERSION(3, 0) + __sync_synchronize (); +#endif + + if (head < tail) + tail = head; + + while ((head - tail) >= sizeof (struct perf_event_header)) + { + g_autofree guint8 *free_me = NULL; + const SysprofPerfEvent *event; + struct perf_event_header *header; + guint8 buffer[4096]; + gboolean is_self = FALSE; + + /* Note that: + * + * - perf events are a multiple of 64 bits + * - the perf event header is 64 bits + * - the data area is a multiple of 64 bits + * + * which means there will always be space for one header, which means we + * can safely dereference the size field. + */ + header = (struct perf_event_header *)(gpointer)(self->map_data + (tail & mask)); + + if (header->size > head - tail) + { + /* The kernel did not generate a complete event. + * I don't think that can happen, but we may as well + * be paranoid. + */ + break; + } + + if (self->map_data + (tail & mask) + header->size > self->map_data + n_bytes) + { + gint n_before; + gint n_after; + guint8 *b; + + if (header->size > sizeof buffer) + free_me = b = g_malloc (header->size); + else + b = buffer; + + n_after = (tail & mask) + header->size - n_bytes; + n_before = header->size - n_after; + + memcpy (b, self->map_data + (tail & mask), n_before); + memcpy (b + n_before, self->map_data, n_after); + + header = (struct perf_event_header *)(gpointer)b; + } + + event = (SysprofPerfEvent *)header; + + switch (event->header.type) + { + default: + case PERF_RECORD_COMM: + case PERF_RECORD_EXIT: + case PERF_RECORD_FORK: + break; + + case PERF_RECORD_SAMPLE: + is_self = event->callchain.pid == self->self_pid; + break; + + case PERF_RECORD_READ: + case PERF_RECORD_THROTTLE: + case PERF_RECORD_UNTHROTTLE: + goto skip_callback; + + case PERF_RECORD_LOST: + lost_records = TRUE; + g_debug ("Lost records from perf"); + break; + } + + if (self->callback != NULL) + self->callback (event, self->cpu, self->callback_data); + + us += is_self; + them += !is_self; + + skip_callback: + tail += header->size; + } + + self->tail = tail; + +#ifdef HAVE_STDATOMIC_H + atomic_thread_fence (memory_order_seq_cst); +#elif G_GNUC_CHECK_VERSION(3, 0) + __sync_synchronize (); +#endif + + self->map->data_tail = tail; + + /* If we lost records them we took too long to process events and + * need to speed up how often we process incoming records. However, + * if we are the cause of that (due to running to frequently), then + * we need to back-off. + */ + if (lost_records && us < them/3) + { + if (source->timeout_msec > 1) + source->timeout_msec -= 5; + } + else + { + if (source->timeout_msec < 500) + { + if (us < 5 && them < 5) + source->timeout_msec += 100; + else + source->timeout_msec += 10; + } + } +} + +static gboolean +sysprof_perf_event_source_dispatch (GSource *gsource, + GSourceFunc callback, + gpointer user_data) +{ + SysprofPerfEventSource *source = (SysprofPerfEventSource *)gsource; + SysprofPerfEventStream *self = source->stream; + + if (source->next_ready_time <= g_source_get_time (gsource) && + self != NULL && + self->active && + self->map != NULL && + self->tail != self->map->data_head) + sysprof_perf_event_stream_flush (self); + else + source->timeout_msec = MIN (500, source->timeout_msec + 50); + + source->next_ready_time = g_get_monotonic_time () + (source->timeout_msec * 1000); + g_source_set_ready_time (self->source, source->next_ready_time); + + return G_SOURCE_CONTINUE; +} + +static GSourceFuncs source_funcs = { + .dispatch = sysprof_perf_event_source_dispatch, +}; + +static void +sysprof_perf_event_stream_finalize (GObject *object) +{ + SysprofPerfEventStream *self = (SysprofPerfEventStream *)object; + + if (self->callback_data_destroy) + { + self->callback_data_destroy (self->callback_data); + self->callback_data_destroy = NULL; + self->callback_data = NULL; + } + + self->callback = NULL; + + dex_clear (&self->promise); + + g_clear_object (&self->connection); + + if (self->source != NULL) + { + g_source_destroy (self->source); + g_source_unref (self->source); + self->source = NULL; + } + + g_clear_fd (&self->perf_fd, NULL); + + G_OBJECT_CLASS (sysprof_perf_event_stream_parent_class)->finalize (object); +} + +static void +sysprof_perf_event_stream_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofPerfEventStream *self = SYSPROF_PERF_EVENT_STREAM (object); + + switch (prop_id) + { + case PROP_ACTIVE: + g_value_set_boolean (value, self->active); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_perf_event_stream_class_init (SysprofPerfEventStreamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_perf_event_stream_finalize; + object_class->get_property = sysprof_perf_event_stream_get_property; + + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_perf_event_stream_init (SysprofPerfEventStream *self) +{ + self->perf_fd = -1; + self->self_pid = getpid (); +} + +static void +sysprof_perf_event_stream_new_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = (GDBusConnection *)object; + g_autoptr(SysprofPerfEventStream) self = user_data; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) error = NULL; + + g_assert (G_IS_DBUS_CONNECTION (connection)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_PERF_EVENT_STREAM (self)); + + if ((ret = g_dbus_connection_call_with_unix_fd_list_finish (connection, &fd_list, result, &error))) + { + int handle; + int fd; + + g_variant_get (ret, "(h)", &handle); + + if (-1 != (fd = g_unix_fd_list_get (fd_list, handle, &error))) + { + gsize map_size; + guint8 *map; + + self->perf_fd = fd; + + map_size = N_PAGES * sysprof_getpagesize () + sysprof_getpagesize (); + map = mmap (NULL, map_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + + if ((gpointer)map == MAP_FAILED) + { + int errsv = errno; + g_set_error_literal (&error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + } + else + { + self->map = (gpointer)map; + self->map_data = map + sysprof_getpagesize (); + self->tail = 0; + } + } + } + + if (error != NULL) + dex_promise_reject (self->promise, g_steal_pointer (&error)); + else + dex_promise_resolve_object (self->promise, g_object_ref (self)); + + dex_clear (&self->promise); +} + +DexFuture * +sysprof_perf_event_stream_new (GDBusConnection *connection, + struct perf_event_attr *attr, + int cpu, + int group_fd, + guint64 flags, + SysprofPerfEventCallback callback, + gpointer callback_data, + GDestroyNotify callback_data_destroy) +{ + SysprofPerfEventSource *source; + g_autoptr(SysprofPerfEventStream) self = NULL; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autoptr(DexPromise) promise = NULL; + g_autoptr(GVariant) options = NULL; + g_autofree char *name = NULL; + int group_fd_handle = -1; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (attr != NULL, NULL); + g_return_val_if_fail (cpu > -1, NULL); + g_return_val_if_fail (group_fd >= -1, NULL); + + promise = dex_promise_new (); + + self = g_object_new (SYSPROF_TYPE_PERF_EVENT_STREAM, NULL); + self->connection = g_object_ref (connection); + self->attr = *attr; + self->cpu = cpu; + self->group_fd = group_fd; + self->flags = flags; + self->callback = callback; + self->callback_data = callback_data; + self->callback_data_destroy = callback_data_destroy; + self->promise = dex_ref (promise); + + source = (SysprofPerfEventSource *) + g_source_new (&source_funcs, sizeof (SysprofPerfEventSource)); + source->stream = self; + source->timeout_msec = 5; + source->next_ready_time = g_get_monotonic_time () + (source->timeout_msec * 1000); + self->source = (GSource *)source; + + name = g_strdup_printf ("[perf cpu%d]", cpu); + + g_source_set_ready_time (self->source, source->next_ready_time); + g_source_set_name (self->source, name); + g_source_attach (self->source, NULL); + + if (group_fd > -1) + { + fd_list = g_unix_fd_list_new (); + group_fd_handle = g_unix_fd_list_append (fd_list, group_fd, NULL); + } + + options = build_options_dict (attr); + + g_dbus_connection_call_with_unix_fd_list (connection, + "org.gnome.Sysprof3", + "/org/gnome/Sysprof3", + "org.gnome.Sysprof3.Service", + "PerfEventOpen", + g_variant_new ("(@a{sv}iiht)", + options, + -1, + cpu, + group_fd_handle, + flags), + G_VARIANT_TYPE ("(h)"), + G_DBUS_CALL_FLAGS_NONE, + G_MAXUINT, + fd_list, + dex_promise_get_cancellable (promise), + sysprof_perf_event_stream_new_cb, + g_object_ref (self)); + + return DEX_FUTURE (g_steal_pointer (&promise)); +} + +gboolean +sysprof_perf_event_stream_enable (SysprofPerfEventStream *self, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_PERF_EVENT_STREAM (self), FALSE); + + if (self->active) + return TRUE; + + if (0 != ioctl (self->perf_fd, PERF_EVENT_IOC_ENABLE)) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + return FALSE; + } + + self->active = TRUE; + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE]); + + return TRUE; +} + +gboolean +sysprof_perf_event_stream_disable (SysprofPerfEventStream *self, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_PERF_EVENT_STREAM (self), FALSE); + + if (!self->active) + return TRUE; + + if (0 != ioctl (self->perf_fd, PERF_EVENT_IOC_DISABLE)) + { + int errsv = errno; + g_set_error_literal (error, + G_IO_ERROR, + g_io_error_from_errno (errsv), + g_strerror (errsv)); + return FALSE; + } + + self->active = FALSE; + + sysprof_perf_event_stream_flush (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE]); + + return TRUE; +} diff --git a/src/libsysprof/sysprof-perf-source.c b/src/libsysprof/sysprof-perf-source.c deleted file mode 100644 index 8d669ed5..00000000 --- a/src/libsysprof/sysprof-perf-source.c +++ /dev/null @@ -1,830 +0,0 @@ -/* sysprof-perf-source.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sysprof-clock.h" -#include "sysprof-helpers.h" -#include "sysprof-line-reader.h" -#include "sysprof-perf-counter.h" -#include "sysprof-perf-source.h" - -#define N_WAKEUP_EVENTS 149 - -/* Identifiers for the various tracepoints we might watch for */ -enum SysprofTracepoint -{ - DRM_VBLANK, - DRM_I915_BEGIN, - DRM_I915_END, -}; - -typedef struct -{ - enum SysprofTracepoint tp; - const char *path; - const char **fields; -} SysprofOptionalTracepoint; - -/* Global list of the optional tracepoints we might want to watch. */ -static const SysprofOptionalTracepoint optional_tracepoints[] = { - /* This event fires just after the vblank IRQ handler starts. - * - * Note that on many platforms when nothing is waiting for vblank - * (no pageflips have happened recently, no rendering is - * synchronizing to vblank), the vblank IRQ will get masked off and - * the event won't show up in the timeline. - * - * Also note that when we're in watch-a-single-process mode, we - * won't get the event since it comes in on an IRQ handler, not for - * our pid. - */ - { DRM_VBLANK, "drm/drm_vblank_event", - (const char *[]){ "crtc", "seq", NULL } }, - - /* I915 GPU execution. - * - * These are the wrong events to be watching. We need to use the - * ones under CONFIG_DRM_I915_LOW_LEVEL_TRACEPOINTS instead. - */ -#if 0 - { DRM_I915_BEGIN, "i915/i915_gem_request_add", - (const char *[]){ "ctx", "ring", "seqno", NULL } }, - { DRM_I915_END, "i915/i915_gem_request_retire", - (const char *[]){ "ctx", "ring", "seqno", NULL } }, -#endif -}; - -/* Struct describing tracepoint events. - * - * This should be extended with some sort of union for the describing - * the locations of the relevant fields within the _RAW section of the - * struct perf_event, so we can pick out things like the vblank CRTC - * number and MSC. - */ -typedef struct { - enum SysprofTracepoint tp; - gsize field_offsets[0]; -} SysprofTracepointDesc; - -struct _SysprofPerfSource -{ - GObject parent_instance; - - SysprofCaptureWriter *writer; - SysprofPerfCounter *counter; - GHashTable *pids; - - /* Mapping from perf sample identifiers to SysprofTracepointDesc. */ - GHashTable *tracepoint_event_ids; - - guint running : 1; - guint is_ready : 1; -}; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofPerfSource, sysprof_perf_source, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -enum { - TARGET_EXITED, - N_SIGNALS -}; - -static guint signals [N_SIGNALS]; - -static void -sysprof_perf_source_real_target_exited (SysprofPerfSource *self) -{ - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - - sysprof_source_emit_finished (SYSPROF_SOURCE (self)); -} - -static void -sysprof_perf_source_finalize (GObject *object) -{ - SysprofPerfSource *self = (SysprofPerfSource *)object; - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - g_clear_pointer (&self->counter, sysprof_perf_counter_unref); - g_clear_pointer (&self->pids, g_hash_table_unref); - g_clear_pointer (&self->tracepoint_event_ids, g_hash_table_unref); - - G_OBJECT_CLASS (sysprof_perf_source_parent_class)->finalize (object); -} - -static void -sysprof_perf_source_class_init (SysprofPerfSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_perf_source_finalize; - - signals [TARGET_EXITED] = - g_signal_new_class_handler ("target-exited", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - G_CALLBACK (sysprof_perf_source_real_target_exited), - NULL, NULL, NULL, G_TYPE_NONE, 0); -} - -static void -sysprof_perf_source_init (SysprofPerfSource *self) -{ - self->pids = g_hash_table_new (NULL, NULL); - self->tracepoint_event_ids = g_hash_table_new (NULL, NULL); -} - -static gboolean -do_emit_exited (gpointer data) -{ - g_autoptr(SysprofPerfSource) self = data; - - g_signal_emit (self, signals [TARGET_EXITED], 0); - - return G_SOURCE_REMOVE; -} - -static void -sysprof_perf_source_handle_tracepoint (SysprofPerfSource *self, - gint cpu, - const SysprofPerfCounterEventTracepoint *sample, - SysprofTracepointDesc *tp_desc) -{ - gchar *message = NULL; - - /* Note that field_offsets[] correspond to the - * SysprofOptionalTracepoint->fields[] strings. Yes, this is gross. - */ - switch (tp_desc->tp) - { - case DRM_VBLANK: - message = g_strdup_printf ("crtc=%d, seq=%u", - *(gint *)(gpointer)(sample->raw + tp_desc->field_offsets[0]), - *(guint *)(gpointer)(sample->raw + tp_desc->field_offsets[1])); - - sysprof_capture_writer_add_mark (self->writer, - sample->time, - cpu, - sample->pid, - 0, - "drm", - "vblank", - message); - break; - - case DRM_I915_BEGIN: - case DRM_I915_END: - message = g_strdup_printf ("ctx=%u, ring=%u, seqno=%u", - *(guint *)(gpointer)(sample->raw + tp_desc->field_offsets[0]), - *(guint *)(gpointer)(sample->raw + tp_desc->field_offsets[1]), - *(guint *)(gpointer)(sample->raw + tp_desc->field_offsets[2])); - - sysprof_capture_writer_add_mark (self->writer, - sample->time, - cpu, - sample->pid, - 0, - "drm", - (tp_desc->tp == DRM_I915_BEGIN ? - "i915 gpu begin" : "i915 gpu end"), - message); - break; - - default: - break; - } - - g_free (message); -} - -static void -sysprof_perf_source_handle_callchain (SysprofPerfSource *self, - gint cpu, - const SysprofPerfCounterEventCallchain *sample) -{ - const guint64 *ips; - gint n_ips; - guint64 trace[3]; - - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - g_assert (sample != NULL); - - ips = sample->ips; - n_ips = sample->n_ips; - - if (n_ips == 0) - { - if (sample->header.misc & PERF_RECORD_MISC_KERNEL) - { - trace[0] = PERF_CONTEXT_KERNEL; - trace[1] = sample->ip; - trace[2] = PERF_CONTEXT_USER; - - ips = trace; - n_ips = 3; - } - else - { - trace[0] = PERF_CONTEXT_USER; - trace[1] = sample->ip; - - ips = trace; - n_ips = 2; - } - } - - sysprof_capture_writer_add_sample (self->writer, - sample->time, - cpu, - sample->pid, - sample->tid, - ips, - n_ips); -} - -static inline void -realign (gsize *pos, - gsize align) -{ - *pos = (*pos + align - 1) & ~(align - 1); -} - -static void -sysprof_perf_source_handle_event (SysprofPerfCounterEvent *event, - guint cpu, - gpointer user_data) -{ - SysprofPerfSource *self = user_data; - SysprofTracepointDesc *tp_desc; - gsize offset; - gint64 time; - - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - g_assert (event != NULL); - - switch (event->header.type) - { - case PERF_RECORD_COMM: - offset = strlen (event->comm.comm) + 1; - realign (&offset, sizeof (guint64)); - offset += sizeof (GPid) + sizeof (GPid); - memcpy (&time, event->comm.comm + offset, sizeof time); - - sysprof_capture_writer_add_process (self->writer, - time, - cpu, - event->comm.pid, - event->comm.comm); - - break; - - case PERF_RECORD_EXIT: - /* Ignore fork exits for now */ - if (event->exit.tid != event->exit.pid) - break; - - sysprof_capture_writer_add_exit (self->writer, - event->exit.time, - cpu, - event->exit.pid); - - if (g_hash_table_contains (self->pids, GINT_TO_POINTER (event->exit.pid))) - { - g_hash_table_remove (self->pids, GINT_TO_POINTER (event->exit.pid)); - - if (self->running && (g_hash_table_size (self->pids) == 0)) - { - self->running = FALSE; - sysprof_perf_counter_disable (self->counter); - g_timeout_add (0, do_emit_exited, g_object_ref (self)); - } - } - - break; - - case PERF_RECORD_FORK: - sysprof_capture_writer_add_fork (self->writer, - event->fork.time, - cpu, - event->fork.ptid, - event->fork.tid); - - /* - * TODO: We should add support for "follow fork" of the GPid if we are - * targetting it. - */ - - break; - - case PERF_RECORD_LOST: - break; - - case PERF_RECORD_MMAP: - offset = strlen (event->mmap.filename) + 1; - realign (&offset, sizeof (guint64)); - offset += sizeof (GPid) + sizeof (GPid); - memcpy (&time, event->mmap.filename + offset, sizeof time); - - sysprof_capture_writer_add_map (self->writer, - time, - cpu, - event->mmap.pid, - event->mmap.addr, - event->mmap.addr + event->mmap.len, - event->mmap.pgoff, - 0, - event->mmap.filename); - - break; - - case PERF_RECORD_READ: - break; - - case PERF_RECORD_SAMPLE: - /* We don't capture IPs with tracepoints, and get _RAW data - * instead. Handle them separately. - */ - g_assert (&event->callchain.identifier == &event->tracepoint.identifier); - tp_desc = g_hash_table_lookup (self->tracepoint_event_ids, - GINT_TO_POINTER (event->callchain.identifier)); - if (tp_desc) - { - sysprof_perf_source_handle_tracepoint (self, cpu, &event->tracepoint, tp_desc); - } - else - { - sysprof_perf_source_handle_callchain (self, cpu, &event->callchain); - } - break; - - case PERF_RECORD_THROTTLE: - case PERF_RECORD_UNTHROTTLE: - default: - break; - } -} - -static gboolean -sysprof_perf_get_tracepoint_config (const char *path, - gint64 *config) -{ - g_autofree gchar *filename = NULL; - g_autofree gchar *contents = NULL; - gsize len; - - filename = g_strdup_printf ("/sys/kernel/debug/tracing/events/%s/id", path); - if (!g_file_get_contents (filename, &contents, &len, NULL)) - return FALSE; - - *config = g_ascii_strtoull (contents, NULL, 10); - - return TRUE; -} - -static gboolean -sysprof_perf_get_tracepoint_fields (SysprofTracepointDesc *tp_desc, - const SysprofOptionalTracepoint *optional_tp, - GError **error) -{ - gchar *filename = NULL; - gchar *contents; - size_t len; - gint i; - filename = g_strdup_printf ("/sys/kernel/debug/tracing/events/%s/format", - optional_tp->path); - if (!filename) - return FALSE; - - if (!g_file_get_contents (filename, &contents, &len, NULL)) - { - g_free (filename); - return FALSE; - } - - g_free (filename); - - /* Look up our fields. Some example strings: - * - * field:unsigned short common_type; offset:0; size:2; signed:0; - * field:int crtc; offset:8; size:4; signed:1; - * field:unsigned int seq; offset:12; size:4; signed:0; - */ - for (i = 0; optional_tp->fields[i] != NULL; i++) - { - gchar *pattern = g_strdup_printf ("%s;\toffset:", optional_tp->fields[i]); - gchar *match; - gint64 offset; - - match = strstr (contents, pattern); - if (!match) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - _("Sysprof failed to find field “%s”."), - optional_tp->fields[i]); - g_free (contents); - return FALSE; - } - - offset = g_ascii_strtoll (match + strlen (pattern), - NULL, 0); - if (offset == G_MININT64 && errno != 0) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - _("Sysprof failed to parse offset for “%s”."), - optional_tp->fields[i]); - g_free (contents); - return FALSE; - } - - tp_desc->field_offsets[i] = offset; - g_free (pattern); - } - - g_free (contents); - - return TRUE; -} - -/* Adds a perf tracepoint event, if it's available. - * - * These are kernel tracepoints that we want to include in our capture - * when present, but may be kernel version or driver-specific. - */ -static void -sysprof_perf_source_add_optional_tracepoint (SysprofPerfSource *self, - GPid pid, - gint cpu, - const SysprofOptionalTracepoint *optional_tracepoint, - GError **error) -{ - struct perf_event_attr attr = { 0 }; - SysprofTracepointDesc *tp_desc; - gulong flags = 0; - gint fd; - gint64 config; - gint64 id; - int ret; - gint num_fields; - - if (!sysprof_perf_get_tracepoint_config(optional_tracepoint->path, &config)) - return; - - attr.type = PERF_TYPE_TRACEPOINT; - attr.sample_type = PERF_SAMPLE_RAW - | PERF_SAMPLE_IP - | PERF_SAMPLE_TID - | PERF_SAMPLE_IDENTIFIER - | PERF_SAMPLE_RAW - | PERF_SAMPLE_TIME; - attr.config = config; - attr.sample_period = 1; - -#ifdef HAVE_PERF_CLOCKID - attr.clockid = sysprof_clock; - attr.use_clockid = 1; -#endif - - attr.size = sizeof attr; - - fd = sysprof_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags); - - ret = ioctl (fd, PERF_EVENT_IOC_ID, &id); - if (ret != 0) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - _("Sysprof failed to get perf_event ID.")); - close(fd); - return; - } - - /* The fields list is NULL-terminated, count how many are there. */ - for (num_fields = 0; optional_tracepoint->fields[num_fields]; num_fields++) - ; - - tp_desc = g_malloc (sizeof (*tp_desc) + - sizeof(*tp_desc->field_offsets) * num_fields); - if (!tp_desc) - { - close(fd); - return; - } - - tp_desc->tp = optional_tracepoint->tp; - - if (!sysprof_perf_get_tracepoint_fields (tp_desc, optional_tracepoint, error)) - { - free(tp_desc); - close(fd); - return; - } - - /* Here's where we should inspect the /format file to determine how - * to pick fields out of the _RAW data. - */ - - /* We're truncating the event ID from 64b to 32 to fit in the hash. - * The event IDs start from 0 at boot, so meh. - */ - g_assert (id <= 0xffffffff); - g_hash_table_insert (self->tracepoint_event_ids, GINT_TO_POINTER (id), tp_desc); -} - -static gboolean -sysprof_perf_source_start_pid (SysprofPerfSource *self, - GPid pid, - GError **error) -{ - struct perf_event_attr attr = { 0 }; - gulong flags = 0; - gint ncpu = g_get_num_processors (); - gint cpu = 0; - gint fd = -1; - - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - - attr.sample_type = PERF_SAMPLE_IP - | PERF_SAMPLE_TID - | PERF_SAMPLE_IDENTIFIER - | PERF_SAMPLE_CALLCHAIN - | PERF_SAMPLE_TIME; - attr.wakeup_events = N_WAKEUP_EVENTS; - attr.disabled = TRUE; - attr.mmap = 1; - attr.comm = 1; - attr.task = 1; - attr.exclude_idle = 1; - attr.sample_id_all = 1; - -#ifdef HAVE_PERF_CLOCKID - attr.clockid = sysprof_clock; - attr.use_clockid = 1; -#endif - - attr.size = sizeof attr; - - if (pid != -1) - { - ncpu = 0; - cpu = -1; - } - - /* Perf won't let us capture on all CPUs on all pids, so we have to - * loop over CPUs if we're not just watching a single pid. - */ - for (; cpu < ncpu; cpu++) - { - attr.type = PERF_TYPE_HARDWARE; - attr.config = PERF_COUNT_HW_CPU_CYCLES; - attr.sample_period = 1200000; - - fd = sysprof_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags); - - if (fd == -1) - { - /* - * We might just not have access to hardware counters, so try to - * gracefully fallback to software counters. - */ - attr.type = PERF_TYPE_SOFTWARE; - attr.config = PERF_COUNT_SW_CPU_CLOCK; - attr.sample_period = 1000000; - - errno = 0; - - fd = sysprof_perf_counter_open (self->counter, &attr, pid, cpu, -1, flags); - - if (fd == -1) - { - g_set_error (error, - G_IO_ERROR, - G_IO_ERROR_FAILED, - _("An error occurred while attempting to access performance counters")); - - sysprof_source_stop (SYSPROF_SOURCE (self)); - - return FALSE; - } - } - - for (guint i = 0; i < G_N_ELEMENTS(optional_tracepoints); i++) - sysprof_perf_source_add_optional_tracepoint (self, pid, cpu, - &optional_tracepoints[i], - error); - } - - return TRUE; -} - -static void -sysprof_perf_source_start (SysprofSource *source) -{ - SysprofPerfSource *self = (SysprofPerfSource *)source; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - - self->counter = sysprof_perf_counter_new (NULL); - - sysprof_perf_counter_set_callback (self->counter, - sysprof_perf_source_handle_event, - self, NULL); - - if (g_hash_table_size (self->pids) > 0) - { - GHashTableIter iter; - gpointer key; - - g_hash_table_iter_init (&iter, self->pids); - - while (g_hash_table_iter_next (&iter, &key, NULL)) - { - GPid pid = GPOINTER_TO_INT (key); - - if (!sysprof_perf_source_start_pid (self, pid, &error)) - { - sysprof_source_emit_failed (source, error); - return; - } - } - } - else - { - if (!sysprof_perf_source_start_pid (self, -1, &error)) - { - sysprof_source_emit_failed (source, error); - return; - } - } - - self->running = TRUE; - - sysprof_perf_counter_enable (self->counter); -} - -static void -sysprof_perf_source_stop (SysprofSource *source) -{ - SysprofPerfSource *self = (SysprofPerfSource *)source; - - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - - if (self->running) - { - self->running = FALSE; - sysprof_perf_counter_disable (self->counter); - } - - g_clear_pointer (&self->counter, sysprof_perf_counter_unref); - - sysprof_source_emit_finished (source); -} - -static void -sysprof_perf_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofPerfSource *self = (SysprofPerfSource *)source; - - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - g_assert (writer != NULL); - - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -sysprof_perf_source_add_pid (SysprofSource *source, - GPid pid) -{ - SysprofPerfSource *self = (SysprofPerfSource *)source; - - g_return_if_fail (SYSPROF_IS_PERF_SOURCE (self)); - g_return_if_fail (pid >= -1); - g_return_if_fail (self->writer == NULL); - - g_hash_table_add (self->pids, GINT_TO_POINTER (pid)); -} - -static void -sysprof_perf_source_auth_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofPerfSource) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_PERF_SOURCE (self)); - - if (!sysprof_helpers_authorize_finish (helpers, result, &error)) - { - sysprof_source_emit_failed (SYSPROF_SOURCE (self), error); - } - else - { - self->is_ready = TRUE; - sysprof_source_emit_ready (SYSPROF_SOURCE (self)); - } -} - -static void -sysprof_perf_source_prepare (SysprofSource *source) -{ - g_assert (SYSPROF_IS_PERF_SOURCE (source)); - - sysprof_helpers_authorize_async (sysprof_helpers_get_default (), - NULL, - sysprof_perf_source_auth_cb, - g_object_ref (source)); -} - -static gboolean -sysprof_perf_source_get_is_ready (SysprofSource *source) -{ - return SYSPROF_PERF_SOURCE (source)->is_ready; -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->start = sysprof_perf_source_start; - iface->stop = sysprof_perf_source_stop; - iface->set_writer = sysprof_perf_source_set_writer; - iface->add_pid = sysprof_perf_source_add_pid; - iface->prepare = sysprof_perf_source_prepare; - iface->get_is_ready = sysprof_perf_source_get_is_ready; -} - -SysprofSource * -sysprof_perf_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_PERF_SOURCE, NULL); -} - -void -sysprof_perf_source_set_target_pid (SysprofPerfSource *self, - GPid pid) -{ - g_return_if_fail (SYSPROF_IS_PERF_SOURCE (self)); - g_return_if_fail (pid >= -1); - - if (pid == -1) - g_hash_table_remove_all (self->pids); - else - sysprof_perf_source_add_pid (SYSPROF_SOURCE (self), pid); -} diff --git a/src/libsysprof/sysprof-podman.h b/src/libsysprof/sysprof-podman-private.h similarity index 93% rename from src/libsysprof/sysprof-podman.h rename to src/libsysprof/sysprof-podman-private.h index 053c6259..95d67350 100644 --- a/src/libsysprof/sysprof-podman.h +++ b/src/libsysprof/sysprof-podman-private.h @@ -1,4 +1,4 @@ -/* sysprof-podman.h +/* sysprof-podman-private.h * * Copyright 2020 Christian Hergert * @@ -26,7 +26,6 @@ G_BEGIN_DECLS typedef struct _SysprofPodman SysprofPodman; -gchar **sysprof_podman_debug_dirs (void); SysprofPodman *sysprof_podman_snapshot_current_user (void); gchar **sysprof_podman_get_layers (SysprofPodman *self, const char *container); diff --git a/src/libsysprof/sysprof-podman.c b/src/libsysprof/sysprof-podman.c index 880bc6cc..81076e8c 100644 --- a/src/libsysprof/sysprof-podman.c +++ b/src/libsysprof/sysprof-podman.c @@ -24,51 +24,7 @@ #include -#include "sysprof-podman.h" - -static const char *debug_dirs[] = { - "/usr/lib/debug", - "/usr/lib32/debug", - "/usr/lib64/debug", -}; - -void -_sysprof_podman_debug_dirs (GPtrArray *dirs) -{ - g_autofree gchar *base_path = NULL; - g_autoptr(GDir) dir = NULL; - const gchar *name; - - g_assert (dirs != NULL); - - base_path = g_build_filename (g_get_user_data_dir (), - "containers", - "storage", - "overlay", - NULL); - - if (!(dir = g_dir_open (base_path, 0, NULL))) - return; - - while ((name = g_dir_read_name (dir))) - { - for (guint i = 0; i < G_N_ELEMENTS (debug_dirs); i++) - { - g_autofree gchar *debug_path = g_build_filename (base_path, name, "diff", debug_dirs[i], NULL); - if (g_file_test (debug_path, G_FILE_TEST_IS_DIR)) - g_ptr_array_add (dirs, g_steal_pointer (&debug_path)); - } - } -} - -gchar ** -sysprof_podman_debug_dirs (void) -{ - GPtrArray *dirs = g_ptr_array_new (); - _sysprof_podman_debug_dirs (dirs); - g_ptr_array_add (dirs, NULL); - return (gchar **)g_ptr_array_free (dirs, FALSE); -} +#include "sysprof-podman-private.h" struct _SysprofPodman { diff --git a/src/libsysprof/sysprof-polkit-private.h b/src/libsysprof/sysprof-polkit-private.h index 03b59f3e..d5af505d 100644 --- a/src/libsysprof/sysprof-polkit-private.h +++ b/src/libsysprof/sysprof-polkit-private.h @@ -1,6 +1,6 @@ /* sysprof-polkit-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,19 +21,14 @@ #pragma once #include +#include +#include G_BEGIN_DECLS -G_GNUC_INTERNAL -void _sysprof_polkit_authorize_for_bus_async (GDBusConnection *bus, - const gchar *policy, - GHashTable *details, - gboolean allow_user_interaction, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -G_GNUC_INTERNAL -gboolean _sysprof_polkit_authorize_for_bus_finish (GAsyncResult *result, - GError **error); +DexFuture *_sysprof_polkit_authorize (GDBusConnection *connection, + const char *policy, + PolkitDetails *details, + gboolean allow_user_interaction); G_END_DECLS diff --git a/src/libsysprof/sysprof-polkit.c b/src/libsysprof/sysprof-polkit.c index fa787f2a..c84cb31d 100644 --- a/src/libsysprof/sysprof-polkit.c +++ b/src/libsysprof/sysprof-polkit.c @@ -1,6 +1,6 @@ /* sysprof-polkit.c * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,24 +18,92 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#define G_LOG_DOMAIN "sysprof-polkit" - #include "config.h" -#if HAVE_POLKIT -# include -#endif - #include "sysprof-polkit-private.h" -#include "sysprof-backport-autocleanups.h" -#if HAVE_POLKIT -typedef struct +static void +sysprof_polkit_authority_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) { - const gchar *policy; - PolkitSubject *subject; - GHashTable *details; - guint allow_user_interaction : 1; + g_autoptr(PolkitAuthority) authority = NULL; + g_autoptr(DexPromise) promise = user_data; + g_autoptr(GError) error = NULL; + + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (DEX_IS_PROMISE (promise)); + + if (!(authority = polkit_authority_get_finish (result, &error))) + dex_promise_reject (promise, g_steal_pointer (&error)); + else + dex_promise_resolve_object (promise, g_steal_pointer (&authority)); +} + +static DexFuture * +_sysprof_polkit_authority (void) +{ + DexPromise *promise = dex_promise_new (); + + polkit_authority_get_async (dex_promise_get_cancellable (DEX_PROMISE (promise)), + sysprof_polkit_authority_cb, + dex_ref (promise)); + + return DEX_FUTURE (promise); +} + +static void +sysprof_polkit_check_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + PolkitAuthority *authority = (PolkitAuthority *)object; + g_autoptr(PolkitAuthorizationResult) res = NULL; + g_autoptr(DexPromise) promise = user_data; + g_autoptr(GError) error = NULL; + + g_assert (POLKIT_IS_AUTHORITY (authority)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (DEX_IS_PROMISE (promise)); + + if (!(res = polkit_authority_check_authorization_finish (authority, result, &error))) + dex_promise_reject (promise, g_steal_pointer (&error)); + else if (!polkit_authorization_result_get_is_authorized (res)) + dex_promise_reject (promise, + g_error_new (G_DBUS_ERROR, + G_DBUS_ERROR_AUTH_FAILED, + "Failed to authorize user credentials")); + else + dex_promise_resolve_boolean (promise, TRUE); +} + +static DexFuture * +_sysprof_polkit_check (PolkitAuthority *authority, + PolkitSubject *subject, + const char *policy, + PolkitDetails *details, + PolkitCheckAuthorizationFlags flags) +{ + DexPromise *promise = dex_promise_new (); + + polkit_authority_check_authorization (authority, + subject, + policy, + details, + flags, + dex_promise_get_cancellable (DEX_PROMISE (promise)), + sysprof_polkit_check_cb, + dex_ref (promise)); + + return DEX_FUTURE (promise); +} + +typedef struct _Authorize +{ + GDBusConnection *connection; + char *policy; + PolkitDetails *details; + guint allow_user_interaction : 1; } Authorize; static void @@ -43,135 +111,71 @@ authorize_free (gpointer data) { Authorize *auth = data; - g_clear_object (&auth->subject); - g_clear_pointer (&auth->details, g_hash_table_unref); - g_slice_free (Authorize, auth); + g_clear_pointer (&auth->policy, g_free); + g_clear_object (&auth->connection); + g_clear_object (&auth->details); + g_free (auth); } -static void -sysprof_polkit_check_authorization_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) +static Authorize * +authorize_new (GDBusConnection *connection, + const char *policy, + PolkitDetails *details, + gboolean allow_user_interaction) { - PolkitAuthority *authority = (PolkitAuthority *)object; - g_autoptr(PolkitAuthorizationResult) res = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; + Authorize *auth; - g_assert (POLKIT_IS_AUTHORITY (authority)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); + auth = g_new0 (Authorize, 1); + g_set_object (&auth->connection, connection); + g_set_object (&auth->details, details); + auth->policy = g_strdup (policy); + auth->allow_user_interaction = !!allow_user_interaction; - if (!(res = polkit_authority_check_authorization_finish (authority, result, &error))) - g_task_return_error (task, g_steal_pointer (&error)); - else if (!polkit_authorization_result_get_is_authorized (res)) - g_task_return_new_error (task, - G_IO_ERROR, - G_IO_ERROR_PROXY_AUTH_FAILED, - "Failed to authorize user credentials"); - else - g_task_return_boolean (task, TRUE); + return auth; } -static void -sysprof_polkit_get_authority_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) +static DexFuture * +authorize_fiber (gpointer data) { g_autoptr(PolkitAuthority) authority = NULL; - g_autoptr(PolkitDetails) details = NULL; + g_autoptr(PolkitSubject) subject = NULL; g_autoptr(GError) error = NULL; - g_autoptr(GTask) task = user_data; - GCancellable *cancellable; - Authorize *auth; + const char *bus_name; + Authorize *auth = data; guint flags = 0; - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (G_IS_TASK (task)); - - cancellable = g_task_get_cancellable (task); - auth = g_task_get_task_data (task); - - g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); g_assert (auth != NULL); - g_assert (POLKIT_IS_SUBJECT (auth->subject)); + g_assert (G_IS_DBUS_CONNECTION (auth->connection)); + g_assert (!auth->details || POLKIT_IS_DETAILS (auth->details)); + g_assert (auth->policy != NULL); - if (!(authority = polkit_authority_get_finish (result, &error))) - { - g_task_return_error (task, g_steal_pointer (&error)); - return; - } + bus_name = g_dbus_connection_get_unique_name (auth->connection); + subject = polkit_system_bus_name_new (bus_name); + + if (!(authority = dex_await_object (_sysprof_polkit_authority (), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); if (auth->allow_user_interaction) flags |= POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION; - if (auth->details != NULL) - { - GHashTableIter iter; - gpointer k, v; + if (!dex_await_boolean (_sysprof_polkit_check (authority, subject, auth->policy, auth->details, flags), &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); - details = polkit_details_new (); - g_hash_table_iter_init (&iter, auth->details); - while (g_hash_table_iter_next (&iter, &k, &v)) - polkit_details_insert (details, k, v); - } - - polkit_authority_check_authorization (authority, - auth->subject, - auth->policy, - details, - flags, - cancellable, - sysprof_polkit_check_authorization_cb, - g_steal_pointer (&task)); + return dex_future_new_for_boolean (TRUE); } -#endif -void -_sysprof_polkit_authorize_for_bus_async (GDBusConnection *bus, - const gchar *policy, - GHashTable *details, - gboolean allow_user_interaction, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +DexFuture * +_sysprof_polkit_authorize (GDBusConnection *connection, + const char *policy, + PolkitDetails *details, + gboolean allow_user_interaction) { - g_autoptr(GTask) task = NULL; -#if HAVE_POLKIT - const gchar *bus_name; - Authorize *auth; -#endif + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (policy != NULL, NULL); + g_return_val_if_fail (!details || POLKIT_IS_DETAILS (details), NULL); - g_return_if_fail (G_IS_DBUS_CONNECTION (bus)); - g_return_if_fail (policy != NULL); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - task = g_task_new (NULL, cancellable, callback, user_data); - g_task_set_source_tag (task, _sysprof_polkit_authorize_for_bus_async); - -#if HAVE_POLKIT - bus_name = g_dbus_connection_get_unique_name (bus); - - auth = g_slice_new0 (Authorize); - auth->subject = polkit_system_bus_name_new (bus_name); - auth->policy = g_intern_string (policy); - auth->details = details ? g_hash_table_ref (details) : NULL; - auth->allow_user_interaction = !!allow_user_interaction; - g_task_set_task_data (task, auth, authorize_free); - - polkit_authority_get_async (cancellable, - sysprof_polkit_get_authority_cb, - g_steal_pointer (&task)); -#else - g_task_return_boolean (task, TRUE); -#endif -} - -gboolean -_sysprof_polkit_authorize_for_bus_finish (GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (G_IS_TASK (result), FALSE); - - return g_task_propagate_boolean (G_TASK (result), error); + return dex_scheduler_spawn (NULL, 0, + authorize_fiber, + authorize_new (connection, policy, details, allow_user_interaction), + authorize_free); } diff --git a/src/libsysprof/sysprof-power-profile.c b/src/libsysprof/sysprof-power-profile.c new file mode 100644 index 00000000..14f51a56 --- /dev/null +++ b/src/libsysprof/sysprof-power-profile.c @@ -0,0 +1,313 @@ +/* sysprof-power-profile.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-power-profile.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +struct _SysprofPowerProfile +{ + SysprofInstrument parent_instance; + SysprofRecording *recording; + char *restore_id; + char *id; +}; + +struct _SysprofPowerProfileClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofPowerProfile, sysprof_power_profile, SYSPROF_TYPE_INSTRUMENT) + +enum { + PROP_0, + PROP_ID, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +static char ** +sysprof_power_profile_list_required_policy (SysprofInstrument *instrument) +{ + static const char *required_policy[] = {"net.hadess.PowerProfiles.switch-profile", NULL}; + + return g_strdupv ((char **)required_policy); +} + +static void +restore_power_profile (char *power_profile) +{ + g_debug ("Restoring performance profile to %s\n", power_profile); + + if (power_profile != NULL) + { + g_autofree char *hold = power_profile; + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) error = NULL; + + if (!(bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL))) + return; + + ret = g_dbus_connection_call_sync (bus, + "net.hadess.PowerProfiles", + "/net/hadess/PowerProfiles", + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "net.hadess.PowerProfiles", + "ActiveProfile", + g_variant_new_string (power_profile)), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, &error); + + if (error != NULL) + g_warning ("Failed to restore performance profile: %s", error->message); + } +} + +static DexFuture * +sysprof_power_profile_release_cb (DexFuture *future, + gpointer user_data) +{ + SysprofPowerProfile *self = user_data; + + g_assert (DEX_IS_FUTURE (future)); + g_assert (SYSPROF_IS_POWER_PROFILE (self)); + + g_clear_pointer (&self->restore_id, restore_power_profile); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_power_profile_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + g_assert (SYSPROF_IS_POWER_PROFILE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + return dex_future_finally (dex_cancellable_new_from_cancellable (cancellable), + sysprof_power_profile_release_cb, + g_object_ref (instrument), + g_object_unref); +} + +static char * +get_string_prop (GVariant *v) +{ + g_autoptr(GVariant) child1 = g_variant_get_child_value (v, 0); + g_autoptr(GVariant) child2 = g_variant_get_child_value (child1, 0); + + return g_variant_dup_string (child2, NULL); +} + +static DexFuture * +sysprof_power_profile_prepare_fiber (gpointer user_data) +{ + SysprofPowerProfile *self = user_data; + g_autoptr(GDBusConnection) bus = NULL; + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_POWER_PROFILE (self)); + g_assert (SYSPROF_IS_RECORDING (self->recording)); + + if (self->id == NULL) + goto failure; + + if (!(bus = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) + goto failure; + + if (!(ret = dex_await_variant (dex_dbus_connection_call (bus, + "net.hadess.PowerProfiles", + "/net/hadess/PowerProfiles", + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new ("(ss)", + "net.hadess.PowerProfiles", + "ActiveProfile"), + G_VARIANT_TYPE ("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1), &error))) + goto failure; + + /* Save the value to be restored after recording */ + g_clear_pointer (&self->restore_id, g_free); + self->restore_id = get_string_prop (ret); + g_clear_pointer (&ret, g_variant_unref); + + /* We don't use "HoldPorfile" here because it is not reliable */ + if (g_strcmp0 (self->id, self->restore_id) != 0) + { + if (!(ret = dex_await_variant (dex_dbus_connection_call (bus, + "net.hadess.PowerProfiles", + "/net/hadess/PowerProfiles", + "org.freedesktop.DBus.Properties", + "Set", + g_variant_new ("(ssv)", + "net.hadess.PowerProfiles", + "ActiveProfile", + g_variant_new_string (self->id)), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1), &error))) + goto failure; + + _sysprof_recording_diagnostic (self->recording, + "Power Profile", + "Power profile temporarily set to %s from %s", + self->id, self->restore_id); + } + else + { + g_clear_pointer (&self->restore_id, g_free); + } + +failure: + if (error != NULL) + _sysprof_recording_diagnostic (self->recording, + "Power Profile", + "Failed to set power profile to “%s”: %s", + self->id, error->message); + + g_clear_object (&self->recording); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_power_profile_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofPowerProfile *self = (SysprofPowerProfile *)instrument; + + g_assert (SYSPROF_IS_POWER_PROFILE (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + g_set_object (&self->recording, recording); + + return dex_scheduler_spawn (NULL, 0, + sysprof_power_profile_prepare_fiber, + g_object_ref (self), + g_object_unref); +} + +static void +sysprof_power_profile_dispose (GObject *object) +{ + SysprofPowerProfile *self = (SysprofPowerProfile *)object; + + g_clear_pointer (&self->id, g_free); + g_clear_pointer (&self->restore_id, g_free); + + G_OBJECT_CLASS (sysprof_power_profile_parent_class)->dispose (object); +} + +static void +sysprof_power_profile_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofPowerProfile *self = SYSPROF_POWER_PROFILE (object); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, sysprof_power_profile_get_id (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_power_profile_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofPowerProfile *self = SYSPROF_POWER_PROFILE (object); + + switch (prop_id) + { + case PROP_ID: + self->id = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_power_profile_class_init (SysprofPowerProfileClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->dispose = sysprof_power_profile_dispose; + object_class->get_property = sysprof_power_profile_get_property; + object_class->set_property = sysprof_power_profile_set_property; + + instrument_class->list_required_policy = sysprof_power_profile_list_required_policy; + instrument_class->prepare = sysprof_power_profile_prepare; + instrument_class->record = sysprof_power_profile_record; + + properties[PROP_ID] = + g_param_spec_string ("id", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_power_profile_init (SysprofPowerProfile *self) +{ +} + +SysprofInstrument * +sysprof_power_profile_new (const char *id) +{ + return g_object_new (SYSPROF_TYPE_POWER_PROFILE, + "id", id, + NULL); +} + +const char * +sysprof_power_profile_get_id (SysprofPowerProfile *self) +{ + g_return_val_if_fail (SYSPROF_IS_POWER_PROFILE (self), NULL); + + return self->id; +} diff --git a/src/libsysprof/sysprof-power-profile.h b/src/libsysprof/sysprof-power-profile.h new file mode 100644 index 00000000..5b676ffd --- /dev/null +++ b/src/libsysprof/sysprof-power-profile.h @@ -0,0 +1,44 @@ +/* sysprof-power-profile.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_POWER_PROFILE (sysprof_power_profile_get_type()) +#define SYSPROF_IS_POWER_PROFILE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_POWER_PROFILE) +#define SYSPROF_POWER_PROFILE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_POWER_PROFILE, SysprofPowerProfile) +#define SYSPROF_POWER_PROFILE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_POWER_PROFILE, SysprofPowerProfileClass) + +typedef struct _SysprofPowerProfile SysprofPowerProfile; +typedef struct _SysprofPowerProfileClass SysprofPowerProfileClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_power_profile_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_power_profile_new (const char *id); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_power_profile_get_id (SysprofPowerProfile *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofPowerProfile, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-preload-source.c b/src/libsysprof/sysprof-preload-source.c deleted file mode 100644 index fa04f773..00000000 --- a/src/libsysprof/sysprof-preload-source.c +++ /dev/null @@ -1,153 +0,0 @@ -/* sysprof-preload-source.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-preload-source.h" - -#include "config.h" - -#include "sysprof-preload-source.h" - -struct _SysprofPreloadSource -{ - GObject parent_instance; - gchar *preload; -}; - -enum { - PROP_0, - PROP_PRELOAD, - N_PROPS -}; - -static void -sysprof_preload_source_modify_spawn (SysprofSource *source, - SysprofSpawnable *spawnable) -{ - SysprofPreloadSource *self = (SysprofPreloadSource *)source; - - g_assert (SYSPROF_IS_SOURCE (self)); - g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); - -#ifdef __linux__ - if (self->preload) - { - g_autofree gchar *freeme = NULL; - const gchar *ld_preload; - - if (!(ld_preload = sysprof_spawnable_getenv (spawnable, "LD_PRELOAD"))) - sysprof_spawnable_setenv (spawnable, "LD_PRELOAD", self->preload); - else - sysprof_spawnable_setenv (spawnable, - "LD_PRELOAD", - (freeme = g_strdup_printf ("%s:%s", self->preload, ld_preload))); - } -#endif -} - -static void -sysprof_preload_source_stop (SysprofSource *source) -{ - sysprof_source_emit_finished (source); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->modify_spawn = sysprof_preload_source_modify_spawn; - iface->stop = sysprof_preload_source_stop; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofPreloadSource, sysprof_preload_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_preload_source_finalize (GObject *object) -{ - SysprofPreloadSource *self = (SysprofPreloadSource *)object; - - g_clear_pointer (&self->preload, g_free); - - G_OBJECT_CLASS (sysprof_preload_source_parent_class)->finalize (object); -} - -static void -sysprof_preload_source_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofPreloadSource *self = SYSPROF_PRELOAD_SOURCE (object); - - switch (prop_id) - { - case PROP_PRELOAD: - g_value_set_string (value, self->preload); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_preload_source_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofPreloadSource *self = SYSPROF_PRELOAD_SOURCE (object); - - switch (prop_id) - { - case PROP_PRELOAD: - g_free (self->preload); - self->preload = g_value_dup_string (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_preload_source_class_init (SysprofPreloadSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_preload_source_finalize; - object_class->get_property = sysprof_preload_source_get_property; - object_class->set_property = sysprof_preload_source_set_property; - - properties [PROP_PRELOAD] = - g_param_spec_string ("preload", - "Preload", - "The preload to load into the process", - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_preload_source_init (SysprofPreloadSource *self) -{ -} diff --git a/src/libsysprof/sysprof-preload-source.h b/src/libsysprof/sysprof-preload-source.h deleted file mode 100644 index 625ca15f..00000000 --- a/src/libsysprof/sysprof-preload-source.h +++ /dev/null @@ -1,32 +0,0 @@ -/* sysprof-preload-source.h - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-source.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PRELOAD_SOURCE (sysprof_preload_source_get_type()) - -SYSPROF_AVAILABLE_IN_3_38 -G_DECLARE_FINAL_TYPE (SysprofPreloadSource, sysprof_preload_source, SYSPROF, PRELOAD_SOURCE, GObject) - -G_END_DECLS diff --git a/src/libsysprof/sysprof-proc-source.c b/src/libsysprof/sysprof-proc-source.c deleted file mode 100644 index b59d1d58..00000000 --- a/src/libsysprof/sysprof-proc-source.c +++ /dev/null @@ -1,519 +0,0 @@ -/* sysprof-proc-source.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "sysprof-helpers.h" -#include "sysprof-podman.h" -#include "sysprof-proc-source.h" - -struct _SysprofProcSource -{ - GObject parent_instance; - SysprofCaptureWriter *writer; - GArray *pids; - SysprofPodman *podman; - guint is_ready : 1; -}; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofProcSource, sysprof_proc_source, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -sysprof_proc_source_populate_process (SysprofProcSource *self, - GPid pid, - const gchar *cmdline) -{ - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (pid > 0); - - sysprof_capture_writer_add_process (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - pid, - cmdline); -} - -static void -sysprof_proc_source_populate_maps (SysprofProcSource *self, - GPid pid, - const gchar *mapsstr, - gboolean ignore_inode) -{ - g_auto(GStrv) lines = NULL; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (mapsstr != NULL); - g_assert (pid > 0); - - lines = g_strsplit (mapsstr, "\n", 0); - - for (guint i = 0; lines[i] != NULL; i++) - { - gchar file[512]; - gulong start; - gulong end; - gulong offset; - gulong inode; - gint r; - - r = sscanf (lines[i], - "%lx-%lx %*15s %lx %*x:%*x %lu %511[^\n]", - &start, &end, &offset, &inode, file); - file [sizeof file - 1] = '\0'; - - /* file has a " (deleted)" suffix if it was deleted from disk */ - if (g_str_has_suffix (file, " (deleted)")) - file [strlen (file) - strlen (" (deleted)")] = '\0'; - - if (r != 5) - continue; - - if (ignore_inode || strcmp ("[vdso]", file) == 0) - { - /* - * Søren Sandmann Pedersen says: - * - * For the vdso, the kernel reports 'offset' as the - * the same as the mapping addres. This doesn't make - * any sense to me, so we just zero it here. There - * is code in binfile.c (read_inode) that returns 0 - * for [vdso]. - */ - offset = 0; - inode = 0; - } - - sysprof_capture_writer_add_map (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - pid, - start, - end, - offset, - inode, - file); - } -} - -static gboolean -pid_is_interesting (SysprofProcSource *self, - GPid pid) -{ - if (self->pids == NULL || self->pids->len == 0) - return TRUE; - - for (guint i = 0; i < self->pids->len; i++) - { - if (g_array_index (self->pids, GPid, i) == pid) - return TRUE; - } - - return FALSE; -} - -static void -add_file (SysprofProcSource *self, - int pid, - const char *path, - const char *data) -{ - gsize to_write = strlen (data); - - while (to_write > 0) - { - gsize this_write = MIN (to_write, 4096 * 2); - to_write -= this_write; - sysprof_capture_writer_add_file (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - pid, - path, - to_write == 0, - (const guint8 *)data, - this_write); - data += this_write; - } -} - -static void -sysprof_proc_source_populate_mountinfo (SysprofProcSource *self, - GPid pid, - const char *mountinfo) -{ - g_autofree char *path = NULL; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (self->writer != NULL); - g_assert (mountinfo != NULL); - - path = g_strdup_printf ("/proc/%d/mountinfo", pid); - add_file (self, pid, path, mountinfo); -} - -static void -sysprof_proc_source_populate_pid_podman (SysprofProcSource *self, - GPid pid, - const char *container) -{ - g_auto(GStrv) layers = NULL; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (container != NULL); - - if (!(layers = sysprof_podman_get_layers (self->podman, container))) - return; - - for (guint i = 0; layers[i]; i++) - sysprof_capture_writer_add_overlay (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, pid, i, layers[i], "/"); -} - -static void -sysprof_proc_source_populate_overlays (SysprofProcSource *self, - GPid pid, - const char *cgroup) -{ - static GRegex *flatpak; - static GRegex *podman; - - g_autoptr(GMatchInfo) flatpak_match = NULL; - g_autoptr(GMatchInfo) podman_match = NULL; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (cgroup != NULL); - - if (strcmp (cgroup, "") == 0) - return; - - /* This function tries to discover the podman container that contains the - * process identified on the host as @pid. We can only do anything with this - * if the pids are in containers that the running user controls (so that we - * can actually access the overlays). - * - * This stuff, and I want to emphasize, is a giant hack. Just like containers - * are on Linux. But if we are really careful, we can make this work for the - * one particular use case I care about, which is podman/toolbox on Fedora - * Workstation/Silverblue. - * - * -- Christian - */ - if G_UNLIKELY (podman == NULL) - { - podman = g_regex_new ("libpod-([a-z0-9]{64})\\.scope", G_REGEX_OPTIMIZE, 0, NULL); - g_assert (podman != NULL); - } - - if (flatpak == NULL) - { - flatpak = g_regex_new ("app-flatpak-([a-zA-Z_\\-\\.]+)-[0-9]+\\.scope", G_REGEX_OPTIMIZE, 0, NULL); - g_assert (flatpak != NULL); - } - - if (g_regex_match (podman, cgroup, 0, &podman_match)) - { - SysprofHelpers *helpers = sysprof_helpers_get_default (); - g_autofree char *word = g_match_info_fetch (podman_match, 1); - g_autofree char *path = g_strdup_printf ("/proc/%d/root/run/.containerenv", pid); - g_autofree char *info = NULL; - - sysprof_proc_source_populate_pid_podman (self, pid, word); - - if (sysprof_helpers_get_proc_file (helpers, path, NULL, &info, NULL)) - add_file (self, pid, "/run/.containerenv", info); - } - else if (g_regex_match (flatpak, cgroup, 0, &flatpak_match)) - { - SysprofHelpers *helpers = sysprof_helpers_get_default (); - g_autofree char *word = g_match_info_fetch (flatpak_match, 1); - g_autofree char *path = g_strdup_printf ("/proc/%d/root/.flatpak-info", pid); - g_autofree char *info = NULL; - - if (sysprof_helpers_get_proc_file (helpers, path, NULL, &info, NULL)) - add_file (self, pid, "/.flatpak-info", info); - } -} - -static void -sysprof_proc_source_populate (SysprofProcSource *self, - GVariant *info) -{ - g_autofree gchar *mounts = NULL; - SysprofHelpers *helpers; - gsize n_pids = 0; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (info != NULL); - g_assert (g_variant_is_of_type (info, G_VARIANT_TYPE ("aa{sv}"))); - - if (self->writer == NULL) - return; - - if (self->podman == NULL) - self->podman = sysprof_podman_snapshot_current_user (); - - helpers = sysprof_helpers_get_default (); - if (!sysprof_helpers_get_proc_file (helpers, "/proc/mounts", NULL, &mounts, NULL)) - return; - - add_file (self, -1, "/proc/mounts", mounts); - - n_pids = g_variant_n_children (info); - for (gsize i = 0; i < n_pids; i++) - { - g_autoptr(GVariant) pidinfo = g_variant_get_child_value (info, i); - GVariantDict dict; - const gchar *cmdline; - const gchar *comm; - const gchar *mountinfo; - const gchar *maps; - const gchar *cgroup; - gboolean ignore_inode; - gint32 pid; - - g_variant_dict_init (&dict, pidinfo); - - if (!g_variant_dict_lookup (&dict, "pid", "i", &pid) || - !pid_is_interesting (self, pid)) - goto skip; - - if (!g_variant_dict_lookup (&dict, "cmdline", "&s", &cmdline)) - cmdline = ""; - - if (!g_variant_dict_lookup (&dict, "comm", "&s", &comm)) - comm = ""; - - if (!g_variant_dict_lookup (&dict, "mountinfo", "&s", &mountinfo)) - mountinfo = ""; - - if (!g_variant_dict_lookup (&dict, "maps", "&s", &maps)) - maps = ""; - - if (!g_variant_dict_lookup (&dict, "cgroup", "&s", &cgroup)) - cgroup = ""; - - /* Ignore inodes from podman/toolbox because they appear - * to always be wrong. We'll have to rely on CRC instead. - */ - ignore_inode = strstr (cgroup, "/libpod-") != NULL; - - sysprof_proc_source_populate_process (self, pid, *cmdline ? cmdline : comm); - sysprof_proc_source_populate_mountinfo (self, pid, mountinfo); - sysprof_proc_source_populate_maps (self, pid, maps, ignore_inode); - sysprof_proc_source_populate_overlays (self, pid, cgroup); - - skip: - g_variant_dict_clear (&dict); - } -} - -static void -sysprof_proc_source_get_process_info_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofProcSource) self = user_data; - g_autoptr(GVariant) info = NULL; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - - if (!sysprof_helpers_get_process_info_finish (helpers, result, &info, &error)) - { - sysprof_source_emit_failed (SYSPROF_SOURCE (self), error); - return; - } - - sysprof_proc_source_populate (self, info); - sysprof_source_emit_finished (SYSPROF_SOURCE (self)); -} - -static void -sysprof_proc_source_start (SysprofSource *source) -{ - SysprofProcSource *self = (SysprofProcSource *)source; - SysprofHelpers *helpers = sysprof_helpers_get_default (); - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (self->writer != NULL); - - sysprof_helpers_get_process_info_async (helpers, - "pid,maps,mountinfo,cmdline,comm,cgroup", - NULL, - sysprof_proc_source_get_process_info_cb, - g_object_ref (self)); -} - -static void -sysprof_proc_source_stop (SysprofSource *source) -{ - SysprofProcSource *self = (SysprofProcSource *)source; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); -} - -static void -sysprof_proc_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofProcSource *self = (SysprofProcSource *)source; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (writer != NULL); - - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -sysprof_proc_source_add_pid (SysprofSource *source, - GPid pid) -{ - SysprofProcSource *self = (SysprofProcSource *)source; - guint i; - - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - g_assert (pid > -1); - - for (i = 0; i < self->pids->len; i++) - { - GPid ele = g_array_index (self->pids, GPid, i); - - if (ele == pid) - return; - } - - g_array_append_val (self->pids, pid); -} - -static void -sysprof_proc_source_auth_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofHelpers *helpers = (SysprofHelpers *)object; - g_autoptr(SysprofProcSource) self = user_data; - g_autoptr(GError) error = NULL; - - g_assert (SYSPROF_IS_HELPERS (helpers)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_PROC_SOURCE (self)); - - if (!sysprof_helpers_authorize_finish (helpers, result, &error)) - { - sysprof_source_emit_failed (SYSPROF_SOURCE (self), error); - } - else - { - self->is_ready = TRUE; - sysprof_source_emit_ready (SYSPROF_SOURCE (self)); - } -} - -static void -sysprof_proc_source_prepare (SysprofSource *source) -{ - g_assert (SYSPROF_IS_PROC_SOURCE (source)); - - sysprof_helpers_authorize_async (sysprof_helpers_get_default (), - NULL, - sysprof_proc_source_auth_cb, - g_object_ref (source)); -} - -static gboolean -sysprof_proc_source_get_is_ready (SysprofSource *source) -{ - return SYSPROF_PROC_SOURCE (source)->is_ready; -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->set_writer = sysprof_proc_source_set_writer; - iface->start = sysprof_proc_source_start; - iface->stop = sysprof_proc_source_stop; - iface->add_pid = sysprof_proc_source_add_pid; - iface->prepare = sysprof_proc_source_prepare; - iface->get_is_ready = sysprof_proc_source_get_is_ready; -} - -static void -sysprof_proc_source_finalize (GObject *object) -{ - SysprofProcSource *self = (SysprofProcSource *)object; - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - g_clear_pointer (&self->pids, g_array_unref); - g_clear_pointer (&self->podman, sysprof_podman_free); - - G_OBJECT_CLASS (sysprof_proc_source_parent_class)->finalize (object); -} - -static void -sysprof_proc_source_class_init (SysprofProcSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_proc_source_finalize; -} - -static void -sysprof_proc_source_init (SysprofProcSource *self) -{ - self->pids = g_array_new (FALSE, FALSE, sizeof (GPid)); -} - -SysprofSource * -sysprof_proc_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_PROC_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-process-info-private.h b/src/libsysprof/sysprof-process-info-private.h new file mode 100644 index 00000000..2a9025e4 --- /dev/null +++ b/src/libsysprof/sysprof-process-info-private.h @@ -0,0 +1,56 @@ +/* sysprof-process-info-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "eggbitset.h" + +#include "sysprof-address-layout-private.h" +#include "sysprof-mount-namespace-private.h" +#include "sysprof-symbol-cache-private.h" + +G_BEGIN_DECLS + +typedef struct _SysprofProcessInfo +{ + SysprofAddressLayout *address_layout; + SysprofMountNamespace *mount_namespace; + SysprofSymbolCache *symbol_cache; + SysprofSymbol *fallback_symbol; + SysprofSymbol *symbol; + EggBitset *thread_ids; + int pid; + gint64 exit_time; +} SysprofProcessInfo; + +SysprofProcessInfo *sysprof_process_info_new (SysprofMountNamespace *mount_namespace, + int pid); +SysprofProcessInfo *sysprof_process_info_ref (SysprofProcessInfo *self); +void sysprof_process_info_unref (SysprofProcessInfo *self); + +static inline void +sysprof_process_info_seen_thread (SysprofProcessInfo *self, + int thread_id) +{ + if (thread_id > 0) + egg_bitset_add (self->thread_ids, thread_id); +} + +G_END_DECLS diff --git a/src/libsysprof/sysprof-process-info.c b/src/libsysprof/sysprof-process-info.c new file mode 100644 index 00000000..6cba4b7b --- /dev/null +++ b/src/libsysprof/sysprof-process-info.c @@ -0,0 +1,85 @@ +/* sysprof-process-info.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-process-info-private.h" +#include "sysprof-symbol-private.h" + +G_DEFINE_BOXED_TYPE (SysprofProcessInfo, + sysprof_process_info, + sysprof_process_info_ref, + sysprof_process_info_unref) + +SysprofProcessInfo * +sysprof_process_info_new (SysprofMountNamespace *mount_namespace, + int pid) +{ + SysprofProcessInfo *self; + char pidstr[32]; + char symname[32]; + + g_snprintf (symname, sizeof symname, "Process %d", pid); + g_snprintf (pidstr, sizeof pidstr, "(%d)", pid); + + self = g_atomic_rc_box_new0 (SysprofProcessInfo); + self->pid = pid; + self->address_layout = sysprof_address_layout_new (); + self->symbol_cache = sysprof_symbol_cache_new (); + self->mount_namespace = mount_namespace; + self->thread_ids = egg_bitset_new_empty (); + self->fallback_symbol = _sysprof_symbol_new (g_ref_string_new (symname), + NULL, + g_ref_string_new (pidstr), + 0, 0, + SYSPROF_SYMBOL_KIND_PROCESS); + + if (pid > 0) + egg_bitset_add (self->thread_ids, pid); + + return self; +} + +SysprofProcessInfo * +sysprof_process_info_ref (SysprofProcessInfo *self) +{ + return g_atomic_rc_box_acquire (self); +} + +static void +sysprof_process_info_finalize (gpointer data) +{ + SysprofProcessInfo *self = data; + + g_clear_object (&self->address_layout); + g_clear_object (&self->symbol_cache); + g_clear_object (&self->mount_namespace); + g_clear_object (&self->fallback_symbol); + g_clear_object (&self->symbol); + g_clear_pointer (&self->thread_ids, egg_bitset_unref); + + self->pid = 0; +} + +void +sysprof_process_info_unref (SysprofProcessInfo *self) +{ + g_atomic_rc_box_release_full (self, sysprof_process_info_finalize); +} diff --git a/src/libsysprof/sysprof-process-model-item.c b/src/libsysprof/sysprof-process-model-item.c deleted file mode 100644 index c46b3c43..00000000 --- a/src/libsysprof/sysprof-process-model-item.c +++ /dev/null @@ -1,255 +0,0 @@ -/* sysprof-process-model-item.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sp-process-model-item" - -#include "config.h" - -#include - -#include "sysprof-process-model-item.h" - -#ifdef __linux__ -# include "sysprof-proc-source.h" -#endif - -struct _SysprofProcessModelItem -{ - GObject parent_instance; - GPid pid; - gchar *command_line; /* Short version (first field) */ - gchar **argv; /* Long version (argv as a strv) */ - guint is_kernel : 1; -}; - -G_DEFINE_TYPE (SysprofProcessModelItem, sysprof_process_model_item, G_TYPE_OBJECT) - -enum { - PROP_0, - PROP_COMMAND_LINE, - PROP_PID, - N_PROPS -}; - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_process_model_item_finalize (GObject *object) -{ - SysprofProcessModelItem *self = (SysprofProcessModelItem *)object; - - g_clear_pointer (&self->command_line, g_free); - g_clear_pointer (&self->argv, g_strfreev); - - G_OBJECT_CLASS (sysprof_process_model_item_parent_class)->finalize (object); -} - -static void -sysprof_process_model_item_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofProcessModelItem *self = SYSPROF_PROCESS_MODEL_ITEM (object); - - switch (prop_id) - { - case PROP_COMMAND_LINE: - g_value_set_string (value, self->command_line); - break; - - case PROP_PID: - g_value_set_int (value, self->pid); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_process_model_item_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofProcessModelItem *self = SYSPROF_PROCESS_MODEL_ITEM (object); - - switch (prop_id) - { - case PROP_COMMAND_LINE: - self->command_line = g_value_dup_string (value); - break; - - case PROP_PID: - self->pid = g_value_get_int (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_process_model_item_class_init (SysprofProcessModelItemClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_process_model_item_finalize; - object_class->get_property = sysprof_process_model_item_get_property; - object_class->set_property = sysprof_process_model_item_set_property; - - properties [PROP_COMMAND_LINE] = - g_param_spec_string ("command-line", - "Command Line", - "Command Line", - NULL, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_PID] = - g_param_spec_int ("pid", - "Pid", - "Pid", - -1, - G_MAXINT, - -1, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_process_model_item_init (SysprofProcessModelItem *self) -{ -} - -SysprofProcessModelItem * -sysprof_process_model_item_new_from_variant (GVariant *info) -{ - SysprofProcessModelItem *ret; - GVariantDict dict; - const gchar *cmdline; - - g_return_val_if_fail (info != NULL, NULL); - g_return_val_if_fail (g_variant_is_of_type (info, G_VARIANT_TYPE_VARDICT), NULL); - - ret = g_object_new (SYSPROF_TYPE_PROCESS_MODEL_ITEM, NULL); - - g_variant_dict_init (&dict, info); - - if (g_variant_dict_lookup (&dict, "cmdline", "&s", &cmdline) && *cmdline) - { - if (g_shell_parse_argv (cmdline, NULL, &ret->argv, NULL)) - ret->command_line = g_strdup (ret->argv[0]); - } - else if (g_variant_dict_lookup (&dict, "comm", "&s", &cmdline)) - { - ret->argv = g_new0 (gchar *, 2); - ret->argv[0] = g_strdup (cmdline); - ret->is_kernel = TRUE; - } - - g_variant_dict_lookup (&dict, "pid", "i", &ret->pid); - g_variant_dict_clear (&dict); - - return g_steal_pointer (&ret); -} - -guint -sysprof_process_model_item_hash (SysprofProcessModelItem *self) -{ - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ITEM (self), 0); - - return self->pid; -} - -gboolean -sysprof_process_model_item_equal (SysprofProcessModelItem *self, - SysprofProcessModelItem *other) -{ - g_assert (SYSPROF_IS_PROCESS_MODEL_ITEM (self)); - g_assert (SYSPROF_IS_PROCESS_MODEL_ITEM (other)); - - return ((self->pid == other->pid) && - (g_strcmp0 (self->command_line, other->command_line) == 0)); -} - -GPid -sysprof_process_model_item_get_pid (SysprofProcessModelItem *self) -{ - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ITEM (self), 0); - - return self->pid; -} - -const gchar * -sysprof_process_model_item_get_command_line (SysprofProcessModelItem *self) -{ - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ITEM (self), NULL); - - return self->command_line; -} - -gboolean -sysprof_process_model_item_is_kernel (SysprofProcessModelItem *self) -{ - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ITEM (self), FALSE); - - return self->is_kernel; -} - -const gchar * const * -sysprof_process_model_item_get_argv (SysprofProcessModelItem *self) -{ - g_autofree gchar *contents = NULL; - g_autofree gchar *path = NULL; - const gchar *pos; - const gchar *endptr; - GPtrArray *ar; - gsize size = 0; - GPid pid; - - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL_ITEM (self), NULL); - - if (self->argv) - return (const gchar * const *)self->argv; - - if ((pid = sysprof_process_model_item_get_pid (self)) < 0) - return NULL; - - path = g_strdup_printf ("/proc/%u/cmdline", (guint)pid); - if (!g_file_get_contents (path, &contents, &size, NULL)) - return NULL; - - ar = g_ptr_array_new (); - - /* Each parameter is followed by \0 */ - for (pos = contents, endptr = contents + size; - pos < endptr; - pos += strlen (pos) + 1) - g_ptr_array_add (ar, g_strdup (pos)); - g_ptr_array_add (ar, NULL); - - g_clear_pointer (&self->argv, g_strfreev); - self->argv = (gchar **)g_ptr_array_free (ar, FALSE); - - return (const gchar * const *)self->argv; -} - diff --git a/src/libsysprof/sysprof-process-model-item.h b/src/libsysprof/sysprof-process-model-item.h deleted file mode 100644 index 2b22ec4a..00000000 --- a/src/libsysprof/sysprof-process-model-item.h +++ /dev/null @@ -1,54 +0,0 @@ -/* sysprof-process-model-item.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-version-macros.h" - -#include - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PROCESS_MODEL_ITEM (sysprof_process_model_item_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofProcessModelItem, sysprof_process_model_item, SYSPROF, PROCESS_MODEL_ITEM, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofProcessModelItem *sysprof_process_model_item_new_from_variant (GVariant *info); -SYSPROF_AVAILABLE_IN_ALL -guint sysprof_process_model_item_hash (SysprofProcessModelItem *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_process_model_item_equal (SysprofProcessModelItem *self, - SysprofProcessModelItem *other); -SYSPROF_AVAILABLE_IN_ALL -GPid sysprof_process_model_item_get_pid (SysprofProcessModelItem *self); -SYSPROF_AVAILABLE_IN_ALL -const gchar *sysprof_process_model_item_get_command_line (SysprofProcessModelItem *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_process_model_item_is_kernel (SysprofProcessModelItem *self); -SYSPROF_AVAILABLE_IN_ALL -const gchar * const *sysprof_process_model_item_get_argv (SysprofProcessModelItem *self); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-process-model.c b/src/libsysprof/sysprof-process-model.c deleted file mode 100644 index ec7e676d..00000000 --- a/src/libsysprof/sysprof-process-model.c +++ /dev/null @@ -1,307 +0,0 @@ -/* sysprof-process-model.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "sysprof-backport-autocleanups.h" -#include "sysprof-helpers.h" -#include "sysprof-process-model.h" -#include "sysprof-process-model-item.h" - -#define QUEUE_RELOAD_TIMEOUT_MSEC 100 - -struct _SysprofProcessModel -{ - GObject parent_instance; - GPtrArray *items; - guint reload_source; - guint no_proxy : 1; -}; - -static void list_model_iface_init (GListModelInterface *iface); - -G_DEFINE_TYPE_EXTENDED (SysprofProcessModel, sysprof_process_model, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) - -static void -sysprof_process_model_finalize (GObject *object) -{ - SysprofProcessModel *self = (SysprofProcessModel *)object; - - g_clear_handle_id (&self->reload_source, g_source_remove); - g_clear_pointer (&self->items, g_ptr_array_unref); - - G_OBJECT_CLASS (sysprof_process_model_parent_class)->finalize (object); -} - -static void -sysprof_process_model_class_init (SysprofProcessModelClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_process_model_finalize; -} - -static void -sysprof_process_model_init (SysprofProcessModel *self) -{ - self->items = g_ptr_array_new_with_free_func (g_object_unref); -} - -static guint -find_index (GPtrArray *ar, - GPid pid) -{ - guint i; - - g_assert (ar != NULL); - - for (i = 0; i < ar->len; i++) - { - SysprofProcessModelItem *item = g_ptr_array_index (ar, i); - GPid item_pid = sysprof_process_model_item_get_pid (item); - - g_assert (pid != item_pid); - - if (item_pid > pid) - return i; - } - - return ar->len; -} - -static void -sysprof_process_model_merge_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofProcessModel *self = (SysprofProcessModel *)object; - g_autoptr(GPtrArray) ret = NULL; - g_autoptr(GHashTable) old_hash = NULL; - g_autoptr(GHashTable) new_hash = NULL; - GError *error = NULL; - guint i; - - g_assert (SYSPROF_IS_PROCESS_MODEL (self)); - g_assert (G_IS_TASK (result)); - - ret = g_task_propagate_pointer (G_TASK (result), &error); - - if (ret == NULL) - { - g_warning ("%s", error->message); - g_clear_error (&error); - return; - } - - /* - * TODO: Clearly this could be optimized to walk both arrays at once - * and do a proper 2-way merge. - */ - - old_hash = g_hash_table_new ((GHashFunc)sysprof_process_model_item_hash, - (GEqualFunc)sysprof_process_model_item_equal); - new_hash = g_hash_table_new ((GHashFunc)sysprof_process_model_item_hash, - (GEqualFunc)sysprof_process_model_item_equal); - - for (i = 0; i < self->items->len; i++) - { - SysprofProcessModelItem *item = g_ptr_array_index (self->items, i); - - g_hash_table_insert (old_hash, item, NULL); - } - - for (i = 0; i < ret->len; i++) - { - SysprofProcessModelItem *item = g_ptr_array_index (ret, i); - - g_hash_table_insert (new_hash, item, NULL); - } - - for (i = self->items->len; i > 0; i--) - { - guint index = i - 1; - SysprofProcessModelItem *item = g_ptr_array_index (self->items, index); - - if (!g_hash_table_contains (new_hash, item)) - { - g_ptr_array_remove_index (self->items, index); - g_list_model_items_changed (G_LIST_MODEL (self), index, 1, 0); - } - } - - for (i = 0; i < ret->len; i++) - { - SysprofProcessModelItem *item = g_ptr_array_index (ret, i); - GPid pid; - guint index; - - if (g_hash_table_contains (old_hash, item)) - continue; - - pid = sysprof_process_model_item_get_pid (item); - index = find_index (self->items, pid); - - g_ptr_array_insert (self->items, index, g_object_ref (item)); - g_list_model_items_changed (G_LIST_MODEL (self), index, 0, 1); - } -} - -static gint -compare_by_pid (gconstpointer a, - gconstpointer b) -{ - SysprofProcessModelItem **aitem = (SysprofProcessModelItem **)a; - SysprofProcessModelItem **bitem = (SysprofProcessModelItem **)b; - - return sysprof_process_model_item_get_pid (*aitem) - sysprof_process_model_item_get_pid (*bitem); -} - -static void -sysprof_process_model_reload_worker (GTask *task, - gpointer source_object, - gpointer task_data, - GCancellable *cancellable) -{ - SysprofProcessModel *self = source_object; - SysprofHelpers *helpers = sysprof_helpers_get_default (); - g_autoptr(GPtrArray) ret = NULL; - g_autoptr(GVariant) info = NULL; - - g_assert (SYSPROF_IS_PROCESS_MODEL (source_object)); - g_assert (G_IS_TASK (task)); - - ret = g_ptr_array_new_with_free_func (g_object_unref); - - if (sysprof_helpers_get_process_info (helpers, "pid,cmdline,comm", self->no_proxy, NULL, &info, NULL)) - { - gsize n_children = g_variant_n_children (info); - - for (gsize i = 0; i < n_children; i++) - { - g_autoptr(GVariant) pidinfo = g_variant_get_child_value (info, i); - g_autoptr(SysprofProcessModelItem) item = sysprof_process_model_item_new_from_variant (pidinfo); - - if (sysprof_process_model_item_is_kernel (item)) - continue; - - g_ptr_array_add (ret, g_steal_pointer (&item)); - } - - g_ptr_array_sort (ret, compare_by_pid); - } - - g_task_return_pointer (task, - g_steal_pointer (&ret), - (GDestroyNotify)g_ptr_array_unref); -} - -static gboolean -sysprof_process_model_do_reload (gpointer user_data) -{ - SysprofProcessModel *self = user_data; - g_autoptr(GTask) task = NULL; - - g_clear_handle_id (&self->reload_source, g_source_remove); - - task = g_task_new (self, NULL, sysprof_process_model_merge_cb, NULL); - g_task_set_priority (task, G_PRIORITY_LOW); - g_task_run_in_thread (task, sysprof_process_model_reload_worker); - - return G_SOURCE_REMOVE; -} - -SysprofProcessModel * -sysprof_process_model_new (void) -{ - return g_object_new (SYSPROF_TYPE_PROCESS_MODEL, NULL); -} - -void -sysprof_process_model_reload (SysprofProcessModel *self) -{ - g_autoptr(GTask) task = NULL; - - g_return_if_fail (SYSPROF_IS_PROCESS_MODEL (self)); - - g_clear_handle_id (&self->reload_source, g_source_remove); - - task = g_task_new (self, NULL, NULL, NULL); - g_task_set_priority (task, G_PRIORITY_LOW); - g_task_run_in_thread_sync (task, sysprof_process_model_reload_worker); - - sysprof_process_model_merge_cb (G_OBJECT (self), G_ASYNC_RESULT (task), NULL); -} - -void -sysprof_process_model_queue_reload (SysprofProcessModel *self) -{ - g_return_if_fail (SYSPROF_IS_PROCESS_MODEL (self)); - - if (self->reload_source == 0) - self->reload_source = g_timeout_add (QUEUE_RELOAD_TIMEOUT_MSEC, - sysprof_process_model_do_reload, - self); -} - -static GType -sysprof_process_model_get_item_type (GListModel *model) -{ - return SYSPROF_TYPE_PROCESS_MODEL_ITEM; -} - -static guint -sysprof_process_model_get_n_items (GListModel *model) -{ - SysprofProcessModel *self = (SysprofProcessModel *)model; - - return self->items->len; -} - -static gpointer -sysprof_process_model_get_item (GListModel *model, - guint position) -{ - SysprofProcessModel *self = (SysprofProcessModel *)model; - - g_return_val_if_fail (SYSPROF_IS_PROCESS_MODEL (self), NULL); - g_return_val_if_fail (position < self->items->len, NULL); - - return g_object_ref (g_ptr_array_index (self->items, position)); -} - -static void -list_model_iface_init (GListModelInterface *iface) -{ - iface->get_item_type = sysprof_process_model_get_item_type; - iface->get_n_items = sysprof_process_model_get_n_items; - iface->get_item = sysprof_process_model_get_item; -} - -void -sysprof_process_model_set_no_proxy (SysprofProcessModel *self, - gboolean no_proxy) -{ - g_return_if_fail (SYSPROF_IS_PROCESS_MODEL (self)); - - self->no_proxy = !!no_proxy; -} diff --git a/src/libsysprof/sysprof-process-model.h b/src/libsysprof/sysprof-process-model.h deleted file mode 100644 index 9737b774..00000000 --- a/src/libsysprof/sysprof-process-model.h +++ /dev/null @@ -1,48 +0,0 @@ -/* sysprof-process-model.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PROCESS_MODEL (sysprof_process_model_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofProcessModel, sysprof_process_model, SYSPROF, PROCESS_MODEL, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofProcessModel *sysprof_process_model_new (void); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_process_model_set_no_proxy (SysprofProcessModel *self, - gboolean no_proxy); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_process_model_reload (SysprofProcessModel *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_process_model_queue_reload (SysprofProcessModel *self); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-profile.c b/src/libsysprof/sysprof-profile.c deleted file mode 100644 index de1d2203..00000000 --- a/src/libsysprof/sysprof-profile.c +++ /dev/null @@ -1,85 +0,0 @@ -/* sysprof-profile.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-profile.h" - -G_DEFINE_INTERFACE (SysprofProfile, sysprof_profile, G_TYPE_OBJECT) - -static void -dummy_generate (SysprofProfile *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_autoptr(GTask) task = NULL; - - task = g_task_new (self, cancellable, callback, user_data); - g_task_return_boolean (task, TRUE); -} - -static gboolean -dummy_generate_finish (SysprofProfile *self, - GAsyncResult *result, - GError **error) -{ - return g_task_propagate_boolean (G_TASK (result), error); -} - -static void -sysprof_profile_default_init (SysprofProfileInterface *iface) -{ - iface->generate = dummy_generate; - iface->generate_finish = dummy_generate_finish; -} - -void -sysprof_profile_generate (SysprofProfile *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - g_return_if_fail (SYSPROF_IS_PROFILE (self)); - g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - - SYSPROF_PROFILE_GET_IFACE (self)->generate (self, cancellable, callback, user_data); -} - -gboolean -sysprof_profile_generate_finish (SysprofProfile *self, - GAsyncResult *result, - GError **error) -{ - g_return_val_if_fail (SYSPROF_IS_PROFILE (self), FALSE); - g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); - - return SYSPROF_PROFILE_GET_IFACE (self)->generate_finish (self, result, error); -} - -void -sysprof_profile_set_reader (SysprofProfile *self, - SysprofCaptureReader *reader) -{ - g_return_if_fail (SYSPROF_IS_PROFILE (self)); - g_return_if_fail (reader != NULL); - - SYSPROF_PROFILE_GET_IFACE (self)->set_reader (self, reader); -} diff --git a/src/libsysprof/sysprof-profile.h b/src/libsysprof/sysprof-profile.h deleted file mode 100644 index 34b1006a..00000000 --- a/src/libsysprof/sysprof-profile.h +++ /dev/null @@ -1,67 +0,0 @@ -/* sysprof-profile.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-capture-reader.h" -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_PROFILE (sysprof_profile_get_type ()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_INTERFACE (SysprofProfile, sysprof_profile, SYSPROF, PROFILE, GObject) - -struct _SysprofProfileInterface -{ - GTypeInterface parent; - - void (*set_reader) (SysprofProfile *self, - SysprofCaptureReader *reader); - void (*generate) (SysprofProfile *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); - gboolean (*generate_finish) (SysprofProfile *self, - GAsyncResult *result, - GError **error); -}; - -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profile_set_reader (SysprofProfile *self, - SysprofCaptureReader *reader); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profile_generate (SysprofProfile *self, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_profile_generate_finish (SysprofProfile *self, - GAsyncResult *result, - GError **error); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-profiler.c b/src/libsysprof/sysprof-profiler.c index 12fe8b5b..ab3c133e 100644 --- a/src/libsysprof/sysprof-profiler.c +++ b/src/libsysprof/sysprof-profiler.c @@ -1,6 +1,6 @@ /* sysprof-profiler.c * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,295 +18,210 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#define G_LOG_DOMAIN "sysprof-profiler" - #include "config.h" +#include "sysprof-controlfd-instrument-private.h" #include "sysprof-profiler.h" +#include "sysprof-recording-private.h" -G_DEFINE_INTERFACE (SysprofProfiler, sysprof_profiler, G_TYPE_OBJECT) +#ifdef __linux__ +# include "sysprof-linux-instrument-private.h" +#endif -enum { - FAILED, - STOPPED, - N_SIGNALS +struct _SysprofProfiler +{ + GObject parent_instance; + GPtrArray *instruments; + SysprofSpawnable *spawnable; }; -static guint signals [N_SIGNALS]; +enum { + PROP_0, + PROP_SPAWNABLE, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +G_DEFINE_FINAL_TYPE (SysprofProfiler, sysprof_profiler, G_TYPE_OBJECT) static void -sysprof_profiler_default_init (SysprofProfilerInterface *iface) +sysprof_profiler_finalize (GObject *object) { - signals [FAILED] = g_signal_new ("failed", - G_TYPE_FROM_INTERFACE (iface), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (SysprofProfilerInterface, failed), - NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_ERROR); + SysprofProfiler *self = (SysprofProfiler *)object; - signals [STOPPED] = g_signal_new ("stopped", - G_TYPE_FROM_INTERFACE (iface), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (SysprofProfilerInterface, stopped), - NULL, NULL, NULL, - G_TYPE_NONE, 0); + g_clear_pointer (&self->instruments, g_ptr_array_unref); + g_clear_object (&self->spawnable); - g_object_interface_install_property (iface, - g_param_spec_double ("elapsed", - "Elapsed", - "The amount of elapsed time profiling", - 0, - G_MAXDOUBLE, - 0, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boolean ("is-running", - "Is Running", - "If the profiler is currently running.", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boolean ("is-mutable", - "Is Mutable", - "If the profiler can still be prepared.", - TRUE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boolean ("spawn-inherit-environ", - "Sysprofawn Inherit Environ", - "If the spawned child should inherit the parents environment", - TRUE, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boolean ("whole-system", - "Whole System", - "If the whole system should be profiled", - TRUE, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boolean ("spawn", - "Sysprofawn", - "If configured child should be spawned", - TRUE, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boxed ("spawn-argv", - "Sysprofawn Argv", - "The arguments for the spawn child", - G_TYPE_STRV, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_string ("spawn-cwd", - "Spawn Working Directory", - "The directory to spawn the application from", - NULL, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - g_object_interface_install_property (iface, - g_param_spec_boxed ("spawn-env", - "Sysprofawn Environment", - "The environment for the spawn child", - G_TYPE_STRV, - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + G_OBJECT_CLASS (sysprof_profiler_parent_class)->finalize (object); } -gdouble -sysprof_profiler_get_elapsed (SysprofProfiler *self) +static void +sysprof_profiler_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - gdouble value = 0.0; - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), 0.0); - g_object_get (self, "elapsed", &value, NULL); - return value; + SysprofProfiler *self = SYSPROF_PROFILER (object); + + switch (prop_id) + { + case PROP_SPAWNABLE: + g_value_set_object (value, sysprof_profiler_get_spawnable (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } } -gboolean -sysprof_profiler_get_is_running (SysprofProfiler *self) +static void +sysprof_profiler_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - gboolean is_running = FALSE; - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), FALSE); - g_object_get (self, "is-running", &is_running, NULL); - return is_running; + SysprofProfiler *self = SYSPROF_PROFILER (object); + + switch (prop_id) + { + case PROP_SPAWNABLE: + sysprof_profiler_set_spawnable (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } } -gboolean -sysprof_profiler_get_is_mutable (SysprofProfiler *self) +static void +sysprof_profiler_class_init (SysprofProfilerClass *klass) { - gboolean is_mutable = FALSE; - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), FALSE); - g_object_get (self, "is-mutable", &is_mutable, NULL); - return is_mutable; + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_profiler_finalize; + object_class->get_property = sysprof_profiler_get_property; + object_class->set_property = sysprof_profiler_set_property; + + properties [PROP_SPAWNABLE] = + g_param_spec_object ("spawnable", NULL, NULL, + SYSPROF_TYPE_SPAWNABLE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); } -gboolean -sysprof_profiler_get_spawn_inherit_environ (SysprofProfiler *self) +static void +sysprof_profiler_init (SysprofProfiler *self) { - gboolean spawn_inherit_environ = FALSE; - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), FALSE); - g_object_get (self, "spawn-inherit-environ", &spawn_inherit_environ, NULL); - return spawn_inherit_environ; + self->instruments = g_ptr_array_new_with_free_func (g_object_unref); + +#ifdef __linux__ + sysprof_profiler_add_instrument (self, _sysprof_linux_instrument_new ()); +#endif + + sysprof_profiler_add_instrument (self, _sysprof_controlfd_instrument_new ()); } +SysprofProfiler * +sysprof_profiler_new (void) +{ + return g_object_new (SYSPROF_TYPE_PROFILER, NULL); +} + +/** + * sysprof_profiler_add_instrument: + * @self: a #SysprofProfiler + * @instrument: (transfer full): a #SysprofInstrument + * + * Adds @instrument to @profiler. + * + * When the recording session is started, @instrument will be directed to + * capture data into the destination capture file. + */ void -sysprof_profiler_set_spawn_inherit_environ (SysprofProfiler *self, - gboolean spawn_inherit_environ) +sysprof_profiler_add_instrument (SysprofProfiler *self, + SysprofInstrument *instrument) { g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_object_set (self, "spawn-inherit-environ", !!spawn_inherit_environ, NULL); -} + g_return_if_fail (SYSPROF_IS_INSTRUMENT (instrument)); -gboolean -sysprof_profiler_get_spawn (SysprofProfiler *self) -{ - gboolean spawn = FALSE; - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), FALSE); - g_object_get (self, "spawn", &spawn, NULL); - return spawn; + g_ptr_array_add (self->instruments, instrument); } void -sysprof_profiler_set_spawn_cwd (SysprofProfiler *self, - const gchar *spawn_cwd) +sysprof_profiler_record_async (SysprofProfiler *self, + SysprofCaptureWriter *writer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_object_set (self, "spawn-cwd", spawn_cwd, NULL); -} + g_autoptr(SysprofRecording) recording = NULL; + g_autoptr(GTask) task = NULL; -void -sysprof_profiler_set_spawn (SysprofProfiler *self, - gboolean spawn) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_object_set (self, "spawn", !!spawn, NULL); -} - -gboolean -sysprof_profiler_get_whole_system (SysprofProfiler *self) -{ - gboolean whole_system = FALSE; - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), FALSE); - g_object_get (self, "whole-system", &whole_system, NULL); - return whole_system; -} - -void -sysprof_profiler_set_whole_system (SysprofProfiler *self, - gboolean whole_system) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_object_set (self, "whole-system", !!whole_system, NULL); -} - -void -sysprof_profiler_set_spawn_argv (SysprofProfiler *self, - const gchar * const *spawn_argv) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_object_set (self, "spawn-argv", spawn_argv, NULL); -} - -void -sysprof_profiler_set_spawn_env (SysprofProfiler *self, - const gchar * const *spawn_env) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_object_set (self, "spawn-env", spawn_env, NULL); -} - -void -sysprof_profiler_add_source (SysprofProfiler *self, - SysprofSource *source) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_return_if_fail (SYSPROF_IS_SOURCE (source)); - - SYSPROF_PROFILER_GET_IFACE (self)->add_source (self, source); -} - -void -sysprof_profiler_set_writer (SysprofProfiler *self, - SysprofCaptureWriter *writer) -{ g_return_if_fail (SYSPROF_IS_PROFILER (self)); g_return_if_fail (writer != NULL); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); - SYSPROF_PROFILER_GET_IFACE (self)->set_writer (self, writer); + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, sysprof_profiler_record_async); + + recording = _sysprof_recording_new (writer, + self->spawnable, + (SysprofInstrument **)self->instruments->pdata, + self->instruments->len); + + g_task_return_pointer (task, g_object_ref (recording), g_object_unref); + + _sysprof_recording_start (recording); } -SysprofCaptureWriter * -sysprof_profiler_get_writer (SysprofProfiler *self) +/** + * sysprof_profiler_record_finish: + * @self: a #SysprofProfiler + * @result: a #GAsyncResult + * @error: a location for a #GError + * + * Completes an asynchronous request to start recording. + * + * Returns: (transfer full): an active #SysprofRecording if successful; + * otherwise %NULL and @error is set. + */ +SysprofRecording * +sysprof_profiler_record_finish (SysprofProfiler *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_PROFILER (self), NULL); + g_return_val_if_fail (G_IS_TASK (result), NULL); + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * sysprof_profiler_get_spawnable: + * @self: a #SysprofProfiler + * + * Gets the #SysprofProfiler:spawnable property. + * + * Returns: (nullable) (transfer none): a #SysprofSpawnable or %NULL + */ +SysprofSpawnable * +sysprof_profiler_get_spawnable (SysprofProfiler *self) { g_return_val_if_fail (SYSPROF_IS_PROFILER (self), NULL); - return SYSPROF_PROFILER_GET_IFACE (self)->get_writer (self); + return self->spawnable; } void -sysprof_profiler_start (SysprofProfiler *self) +sysprof_profiler_set_spawnable (SysprofProfiler *self, + SysprofSpawnable *spawnable) { g_return_if_fail (SYSPROF_IS_PROFILER (self)); + g_return_if_fail (!spawnable || SYSPROF_IS_SPAWNABLE (spawnable)); - SYSPROF_PROFILER_GET_IFACE (self)->start (self); -} - -void -sysprof_profiler_stop (SysprofProfiler *self) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - - SYSPROF_PROFILER_GET_IFACE (self)->stop (self); -} - -void -sysprof_profiler_add_pid (SysprofProfiler *self, - GPid pid) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_return_if_fail (pid > -1); - - SYSPROF_PROFILER_GET_IFACE (self)->add_pid (self, pid); -} - -void -sysprof_profiler_remove_pid (SysprofProfiler *self, - GPid pid) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_return_if_fail (pid > -1); - - SYSPROF_PROFILER_GET_IFACE (self)->remove_pid (self, pid); -} - -const GPid * -sysprof_profiler_get_pids (SysprofProfiler *self, - guint *n_pids) -{ - g_return_val_if_fail (SYSPROF_IS_PROFILER (self), NULL); - g_return_val_if_fail (n_pids != NULL, NULL); - - return SYSPROF_PROFILER_GET_IFACE (self)->get_pids (self, n_pids); -} - -void -sysprof_profiler_emit_failed (SysprofProfiler *self, - const GError *error) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - g_return_if_fail (error != NULL); - - g_signal_emit (self, signals [FAILED], 0, error); -} - -void -sysprof_profiler_emit_stopped (SysprofProfiler *self) -{ - g_return_if_fail (SYSPROF_IS_PROFILER (self)); - - g_signal_emit (self, signals [STOPPED], 0); + if (g_set_object (&self->spawnable, spawnable)) + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SPAWNABLE]); } diff --git a/src/libsysprof/sysprof-profiler.h b/src/libsysprof/sysprof-profiler.h index 24735cdf..42fec019 100644 --- a/src/libsysprof/sysprof-profiler.h +++ b/src/libsysprof/sysprof-profiler.h @@ -1,6 +1,6 @@ /* sysprof-profiler.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,172 +20,40 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif +#include -#include "sysprof-version-macros.h" +#include -#include "sysprof-capture-writer.h" -#include "sysprof-source.h" +#include "sysprof-instrument.h" +#include "sysprof-recording.h" +#include "sysprof-spawnable.h" G_BEGIN_DECLS #define SYSPROF_TYPE_PROFILER (sysprof_profiler_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_INTERFACE (SysprofProfiler, sysprof_profiler, SYSPROF, PROFILER, GObject) - -struct _SysprofProfilerInterface -{ - GTypeInterface parent_interface; - - /** - * SysprofProfiler::failed: - * @self: A #SysprofProfiler - * @reason: A #GError representing the reason for the failure - * - * This signal is emitted if the profiler failed. Note that - * #SysprofProfiler::stopped will also be emitted, but does not allow for - * receiving the error condition. - */ - void (*failed) (SysprofProfiler *self, - const GError *error); - - /** - * SysprofProfiler::stopped: - * @self: A #SysprofProfiler - * - * This signal is emitted when a profiler is stopped. It will always be - * emitted after a sysprof_profiler_start() has been called, either after - * completion of sysprof_profiler_stop() or after a failure or after asynchronous - * completion of stopping. - */ - void (*stopped) (SysprofProfiler *self); - - /** - * SysprofProfiler::add_source: - * - * Adds a source to the profiler. - */ - void (*add_source) (SysprofProfiler *profiler, - SysprofSource *source); - - /** - * SysprofProfiler::set_writer: - * - * Sets the writer to use for the profiler. - */ - void (*set_writer) (SysprofProfiler *self, - SysprofCaptureWriter *writer); - - /** - * SysprofProfiler::get_writer: - * - * Gets the writer that is being used to capture. - * - * Returns: (nullable) (transfer none): A #SysprofCaptureWriter. - */ - SysprofCaptureWriter *(*get_writer) (SysprofProfiler *self); - - /** - * SysprofProfiler::start: - * - * Starts the profiler. - */ - void (*start) (SysprofProfiler *self); - - /** - * SysprofProfiler::stop: - * - * Stops the profiler. - */ - void (*stop) (SysprofProfiler *self); - - /** - * SysprofProfiler::add_pid: - * - * Add a pid to be profiled. - */ - void (*add_pid) (SysprofProfiler *self, - GPid pid); - - /** - * SysprofProfiler::remove_pid: - * - * Remove a pid from the profiler. This will not be called after - * SysprofProfiler::start has been called. - */ - void (*remove_pid) (SysprofProfiler *self, - GPid pid); - - /** - * SysprofProfiler::get_pids: - * - * Gets the pids that are part of this profiling session. If no pids - * have been specified, %NULL is returned. - * - * Returns: (nullable) (transfer none): An array of #GPid, or %NULL. - */ - const GPid *(*get_pids) (SysprofProfiler *self, - guint *n_pids); -}; +G_DECLARE_FINAL_TYPE (SysprofProfiler, sysprof_profiler, SYSPROF, PROFILER, GObject) SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_emit_failed (SysprofProfiler *self, - const GError *error); +SysprofProfiler *sysprof_profiler_new (void); SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_emit_stopped (SysprofProfiler *self); +SysprofSpawnable *sysprof_profiler_get_spawnable (SysprofProfiler *self); SYSPROF_AVAILABLE_IN_ALL -gdouble sysprof_profiler_get_elapsed (SysprofProfiler *self); +void sysprof_profiler_set_spawnable (SysprofProfiler *self, + SysprofSpawnable *spawnable); SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_profiler_get_is_mutable (SysprofProfiler *self); +void sysprof_profiler_add_instrument (SysprofProfiler *self, + SysprofInstrument *instrument); SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_profiler_get_spawn_inherit_environ (SysprofProfiler *self); +void sysprof_profiler_record_async (SysprofProfiler *self, + SysprofCaptureWriter *writer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_spawn_inherit_environ (SysprofProfiler *self, - gboolean spawn_inherit_environ); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_profiler_get_whole_system (SysprofProfiler *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_whole_system (SysprofProfiler *self, - gboolean whole_system); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_profiler_get_spawn (SysprofProfiler *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_spawn (SysprofProfiler *self, - gboolean spawn); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_spawn_argv (SysprofProfiler *self, - const gchar * const *spawn_argv); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_spawn_env (SysprofProfiler *self, - const gchar * const *spawn_env); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_spawn_cwd (SysprofProfiler *self, - const gchar *cwd); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_add_source (SysprofProfiler *self, - SysprofSource *source); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_set_writer (SysprofProfiler *self, - SysprofCaptureWriter *writer); -SYSPROF_AVAILABLE_IN_ALL -SysprofCaptureWriter *sysprof_profiler_get_writer (SysprofProfiler *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_profiler_get_is_running (SysprofProfiler *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_start (SysprofProfiler *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_stop (SysprofProfiler *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_add_pid (SysprofProfiler *self, - GPid pid); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_profiler_remove_pid (SysprofProfiler *self, - GPid pid); -SYSPROF_AVAILABLE_IN_ALL -const GPid *sysprof_profiler_get_pids (SysprofProfiler *self, - guint *n_pids); +SysprofRecording *sysprof_profiler_record_finish (SysprofProfiler *self, + GAsyncResult *result, + GError **error); G_END_DECLS diff --git a/src/libsysprof/sysprof-kernel-symbol.h b/src/libsysprof/sysprof-proxied-instrument-private.h similarity index 64% rename from src/libsysprof/sysprof-kernel-symbol.h rename to src/libsysprof/sysprof-proxied-instrument-private.h index 27c4790a..327e03d2 100644 --- a/src/libsysprof/sysprof-kernel-symbol.h +++ b/src/libsysprof/sysprof-proxied-instrument-private.h @@ -1,6 +1,6 @@ -/* sysprof-kernel-symbol.h +/* sysprof-proxied-instrument-private.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,20 +20,23 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-capture-types.h" +#include "sysprof-instrument-private.h" +#include "sysprof-proxied-instrument.h" G_BEGIN_DECLS -typedef struct +struct _SysprofProxiedInstrument { - SysprofCaptureAddress address; - const gchar *name; -} SysprofKernelSymbol; + SysprofInstrument parent_instance; + GBusType bus_type; + char *bus_name; + char *object_path; + guint call_stop_first : 1; +}; + +struct _SysprofProxiedInstrumentClass +{ + SysprofInstrumentClass parent_class; +}; G_END_DECLS diff --git a/src/libsysprof/sysprof-proxied-instrument.c b/src/libsysprof/sysprof-proxied-instrument.c new file mode 100644 index 00000000..f831421f --- /dev/null +++ b/src/libsysprof/sysprof-proxied-instrument.c @@ -0,0 +1,313 @@ +/* sysprof-proxied-instrument.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-proxied-instrument-private.h" +#include "sysprof-recording-private.h" + + +enum { + PROP_0, + PROP_BUS_TYPE, + PROP_BUS_NAME, + PROP_OBJECT_PATH, + N_PROPS +}; + +G_DEFINE_TYPE (SysprofProxiedInstrument, sysprof_proxied_instrument, SYSPROF_TYPE_INSTRUMENT) + +static GParamSpec *properties [N_PROPS]; + +typedef struct _Record +{ + SysprofRecording *recording; + DexFuture *cancellable; + char *bus_name; + char *object_path; + GBusType bus_type; + guint call_stop_first : 1; +} Record; + +static void +record_free (Record *record) +{ + g_clear_object (&record->recording); + dex_clear (&record->cancellable); + g_clear_pointer (&record->bus_name, g_free); + g_clear_pointer (&record->object_path, g_free); + g_free (record); +} + +static DexFuture * +sysprof_proxied_instrument_record_fiber (gpointer user_data) +{ + Record *record = user_data; + SysprofCaptureWriter *writer; + SysprofCaptureReader *reader; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GUnixFDList) fd_list = NULL; + g_autoptr(DexFuture) started = NULL; + g_autoptr(GError) error = NULL; + GVariantDict options = G_VARIANT_DICT_INIT (NULL); + g_autofd int proxy_fd = -1; + int handle; + + g_assert (record != NULL); + g_assert (record->bus_type != G_BUS_TYPE_NONE); + g_assert (record->bus_name != NULL); + g_assert (record->object_path != NULL); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_CANCELLABLE (record->cancellable)); + + /* Wait for our connection to be available */ + if (!(connection = dex_await_object (dex_bus_get (record->bus_type), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* If we should try calling stop first to cancel any in-flight + * recording. We could pipeline this, but if the other side + * handles messages on threads, they would race. + */ + if (record->call_stop_first) + dex_await (dex_dbus_connection_call (connection, + record->bus_name, + record->object_path, + "org.gnome.Sysprof3.Profiler", + "Stop", + g_variant_new ("()"), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1), + NULL); + + /* Create a new FD that the peer will be able to write to and + * we will concatenate into the capture after recording. + */ + if (-1 == (proxy_fd = sysprof_memfd_create ("[sysprof-proxy]"))) + return dex_future_new_for_errno (errno); + + /* Create FDList to pass to the peer so they can get our FD */ + fd_list = g_unix_fd_list_new (); + if (-1 == (handle = g_unix_fd_list_append (fd_list, proxy_fd, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* Call the proxy and give it our FD to start recording */ + if (!dex_await (dex_dbus_connection_call_with_unix_fd_list (connection, + record->bus_name, + record->object_path, + "org.gnome.Sysprof3.Profiler", + "Start", + g_variant_new ("(@a{sv}h)", + g_variant_dict_end (&options), + handle), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, + G_MAXUINT, + fd_list), + &error)) + { + g_debug ("Failed to start profiler at %s %s: %s", + record->bus_name, + record->object_path, + error->message); + return dex_future_new_for_error (g_steal_pointer (&error)); + } + + /* Await completion of recording */ + dex_await (dex_ref (record->cancellable), NULL); + + /* Call the proxy and let them know to stop */ + dex_await (dex_dbus_connection_call (connection, + record->bus_name, + record->object_path, + "org.gnome.Sysprof3.Profiler", + "Stop", + g_variant_new ("()"), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1), + &error); + + if (error != NULL) + g_warning ("Failed to stop profiler at %s %s: %s", + record->bus_name, + record->object_path, + error->message); + + /* Reset the file position to the start */ + lseek (proxy_fd, 0, SEEK_SET); + + /* Now cat the recording into our writer */ + writer = _sysprof_recording_writer (record->recording); + + if ((reader = sysprof_capture_reader_new_from_fd (g_steal_fd (&proxy_fd)))) + { + sysprof_capture_writer_cat (writer, reader); + sysprof_capture_reader_unref (reader); + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_proxied_instrument_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + SysprofProxiedInstrument *self = (SysprofProxiedInstrument *)instrument; + Record *record; + + g_assert (SYSPROF_IS_PROXIED_INSTRUMENT (self)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + record->bus_name = g_strdup (self->bus_name); + record->object_path = g_strdup (self->object_path); + record->bus_type = self->bus_type; + record->call_stop_first = self->call_stop_first; + + return dex_scheduler_spawn (NULL, 0, + sysprof_proxied_instrument_record_fiber, + record, + (GDestroyNotify)record_free); +} + +static void +sysprof_proxied_instrument_finalize (GObject *object) +{ + SysprofProxiedInstrument *self = (SysprofProxiedInstrument *)object; + + g_clear_pointer (&self->bus_name, g_free); + g_clear_pointer (&self->object_path, g_free); + + G_OBJECT_CLASS (sysprof_proxied_instrument_parent_class)->finalize (object); +} + +static void +sysprof_proxied_instrument_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofProxiedInstrument *self = SYSPROF_PROXIED_INSTRUMENT (object); + + switch (prop_id) + { + case PROP_BUS_TYPE: + g_value_set_enum (value, self->bus_type); + break; + + case PROP_BUS_NAME: + g_value_set_string (value, self->bus_name); + break; + + case PROP_OBJECT_PATH: + g_value_set_string (value, self->object_path); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_proxied_instrument_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofProxiedInstrument *self = SYSPROF_PROXIED_INSTRUMENT (object); + + switch (prop_id) + { + case PROP_BUS_TYPE: + self->bus_type = g_value_get_enum (value); + break; + + case PROP_BUS_NAME: + self->bus_name = g_value_dup_string (value); + break; + + case PROP_OBJECT_PATH: + self->object_path = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_proxied_instrument_class_init (SysprofProxiedInstrumentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->finalize = sysprof_proxied_instrument_finalize; + object_class->get_property = sysprof_proxied_instrument_get_property; + object_class->set_property = sysprof_proxied_instrument_set_property; + + instrument_class->record = sysprof_proxied_instrument_record; + + properties [PROP_BUS_TYPE] = + g_param_spec_enum ("bus-type", NULL, NULL, + G_TYPE_BUS_TYPE, + G_BUS_TYPE_NONE, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BUS_NAME] = + g_param_spec_string ("bus-name", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_OBJECT_PATH] = + g_param_spec_string ("object-path", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_proxied_instrument_init (SysprofProxiedInstrument *self) +{ +} + +SysprofInstrument * +sysprof_proxied_instrument_new (GBusType bus_type, + const char *bus_name, + const char *object_path) +{ + g_return_val_if_fail (bus_type == G_BUS_TYPE_SYSTEM || + bus_type == G_BUS_TYPE_SESSION, NULL); + g_return_val_if_fail (bus_name != NULL, NULL); + g_return_val_if_fail (object_path != NULL, NULL); + + return g_object_new (SYSPROF_TYPE_PROXIED_INSTRUMENT, + "bus-type", bus_type, + "bus-name", bus_name, + "object-path", object_path, + NULL); +} diff --git a/src/libsysprof/sysprof-proxied-instrument.h b/src/libsysprof/sysprof-proxied-instrument.h new file mode 100644 index 00000000..0af100fe --- /dev/null +++ b/src/libsysprof/sysprof-proxied-instrument.h @@ -0,0 +1,46 @@ +/* sysprof-proxied-instrument.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_PROXIED_INSTRUMENT (sysprof_proxied_instrument_get_type()) +#define SYSPROF_IS_PROXIED_INSTRUMENT(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_PROXIED_INSTRUMENT) +#define SYSPROF_PROXIED_INSTRUMENT(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_PROXIED_INSTRUMENT, SysprofProxiedInstrument) +#define SYSPROF_PROXIED_INSTRUMENT_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_PROXIED_INSTRUMENT, SysprofProxiedInstrumentClass) + +typedef struct _SysprofProxiedInstrument SysprofProxiedInstrument; +typedef struct _SysprofProxiedInstrumentClass SysprofProxiedInstrumentClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_proxied_instrument_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_proxied_instrument_new (GBusType bus_type, + const char *bus_name, + const char *object_path); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofProxiedInstrument, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-proxy-source.c b/src/libsysprof/sysprof-proxy-source.c deleted file mode 100644 index f686a19b..00000000 --- a/src/libsysprof/sysprof-proxy-source.c +++ /dev/null @@ -1,753 +0,0 @@ -/* sysprof-proxy-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-proxy-source" - -#include "config.h" - -#include -#include -#include - -#include "sysprof-platform.h" - -#include "sysprof-proxy-source.h" - -struct _SysprofProxySource -{ - GObject parent_instance; - GCancellable *cancellable; - SysprofCaptureWriter *writer; - gchar *bus_name; - gchar *object_path; - GArray *pids; - GPtrArray *monitors; - GBusType bus_type; - guint stopping_count; - guint is_ready : 1; - guint has_started : 1; - guint is_whole_system : 1; -}; - -typedef struct -{ - SysprofProxySource *self; - gchar *name; -} Peer; - -typedef struct -{ - SysprofProxySource *self; - GDBusConnection *bus; - gchar *name; - gchar *object_path; - gint fd; - guint needs_stop : 1; -} Monitor; - -enum { - PROP_0, - PROP_BUS_NAME, - PROP_BUS_TYPE, - PROP_OBJECT_PATH, - N_PROPS -}; - -static GParamSpec *properties[N_PROPS]; - -static inline gint -steal_fd (gint *fd) -{ - gint r = *fd; - *fd = -1; - return r; -} - -static void -_g_weak_ref_free (GWeakRef *wr) -{ - g_weak_ref_clear (wr); - g_slice_free (GWeakRef, wr); -} - -static void -peer_free (Peer *peer) -{ - g_assert (peer != NULL); - - g_clear_object (&peer->self); - g_clear_pointer (&peer->name, g_free); - g_slice_free (Peer, peer); -} - -static void -monitor_free (Monitor *monitor) -{ - if (monitor == NULL) - return; - - if (monitor->needs_stop) - g_dbus_connection_call (monitor->bus, - monitor->name, - monitor->object_path, - "org.gnome.Sysprof3.Profiler", - "Stop", - g_variant_new ("()"), - G_VARIANT_TYPE ("()"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, - NULL, NULL, NULL); - - if (monitor->fd != -1) - { - close (monitor->fd); - monitor->fd = -1; - } - - g_clear_object (&monitor->self); - g_clear_object (&monitor->bus); - g_clear_pointer (&monitor->name, g_free); - g_clear_pointer (&monitor->object_path, g_free); - g_slice_free (Monitor, monitor); -} - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (Peer, peer_free); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (Monitor, monitor_free); - -static void -sysprof_proxy_source_prepare (SysprofSource *source) -{ - g_assert (SYSPROF_IS_PROXY_SOURCE (source)); - - sysprof_source_emit_ready (source); -} - -static gboolean -sysprof_proxy_source_get_is_ready (SysprofSource *source) -{ - g_assert (SYSPROF_IS_PROXY_SOURCE (source)); - - return TRUE; -} - -static void -sysprof_proxy_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofProxySource *self = (SysprofProxySource *)source; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -sysprof_proxy_source_take_monitor (SysprofProxySource *self, - Monitor *monitor) -{ - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (monitor != NULL); - g_assert (monitor->self == self); - g_assert (monitor->bus == NULL || G_IS_DBUS_CONNECTION (monitor->bus)); - - if (g_cancellable_is_cancelled (self->cancellable)) - monitor_free (monitor); - else - g_ptr_array_add (self->monitors, g_steal_pointer (&monitor)); -} - -static void -sysprof_proxy_source_start_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GDBusConnection *bus = (GDBusConnection *)object; - g_autoptr(Monitor) monitor = user_data; - g_autoptr(GVariant) reply = NULL; - g_autoptr(GError) error = NULL; - SysprofProxySource *self; - - g_assert (G_IS_DBUS_CONNECTION (bus)); - g_assert (monitor != NULL); - g_assert (SYSPROF_IS_PROXY_SOURCE (monitor->self)); - g_assert (G_IS_ASYNC_RESULT (result)); - - if (!(reply = g_dbus_connection_call_with_unix_fd_list_finish (bus, NULL, result, &error))) - { - g_dbus_error_strip_remote_error (error); - if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - monitor->needs_stop = TRUE; - g_message ("Failure or no profiler available on peer %s: %s", - monitor->name, error->message); - return; - } - - self = monitor->self; - monitor->needs_stop = TRUE; - sysprof_proxy_source_take_monitor (self, g_steal_pointer (&monitor)); -} - -static void -sysprof_proxy_source_monitor (SysprofProxySource *self, - GDBusConnection *bus, - const gchar *bus_name) -{ - g_autoptr(GUnixFDList) fd_list = NULL; - g_autoptr(GError) error = NULL; - Monitor *monitor; - gint fd; - gint handle; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (G_IS_DBUS_CONNECTION (bus)); - g_assert (bus_name != NULL); - - if (g_cancellable_is_cancelled (self->cancellable)) - return; - - fd_list = g_unix_fd_list_new (); - - if (-1 == (fd = sysprof_memfd_create ("[sysprof-proxy-capture]")) || - -1 == (handle = g_unix_fd_list_append (fd_list, fd, &error))) - { - if (fd != -1) - close (fd); - g_warning ("Failed to create memfd for peer: %s", error->message); - return; - } - - monitor = g_slice_new0 (Monitor); - monitor->self = g_object_ref (self); - monitor->bus = g_object_ref (bus); - monitor->name = g_strdup (bus_name); - monitor->object_path = g_strdup (self->object_path); - monitor->fd = fd; - - g_dbus_connection_call_with_unix_fd_list (bus, - bus_name, - self->object_path, - "org.gnome.Sysprof3.Profiler", - "Start", - g_variant_new ("(a{sv}h)", NULL, handle), - G_VARIANT_TYPE ("()"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, - fd_list, - self->cancellable, - sysprof_proxy_source_start_cb, - g_steal_pointer (&monitor)); -} - -static void -sysprof_proxy_source_get_pid_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GDBusConnection *bus = (GDBusConnection *)object; - g_autoptr(Peer) peer = user_data; - g_autoptr(GVariant) reply = NULL; - g_autoptr(GError) error = NULL; - guint32 pid = 0; - - g_assert (G_IS_DBUS_CONNECTION (bus)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (peer != NULL); - g_assert (SYSPROF_IS_PROXY_SOURCE (peer->self)); - - if (!(reply = g_dbus_connection_call_finish (bus, result, &error))) - return; - - g_variant_get (reply, "(u)", &pid); - - /* If we don't care about this PID, then ignore it */ - for (guint i = 0; i < peer->self->pids->len; i++) - { - if ((GPid)pid == g_array_index (peer->self->pids, GPid, i)) - { - sysprof_proxy_source_monitor (peer->self, bus, peer->name); - return; - } - } -} - -static void -sysprof_proxy_source_list_names_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GDBusConnection *bus = (GDBusConnection *)object; - g_autofree const gchar **names = NULL; - g_autoptr(SysprofProxySource) self = user_data; - g_autoptr(GVariant) reply = NULL; - g_autoptr(GError) error = NULL; - - g_assert (G_IS_DBUS_CONNECTION (bus)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - - if (!(reply = g_dbus_connection_call_finish (bus, result, &error))) - { - g_warning ("Failed to list D-Bus peer names: %s", error->message); - return; - } - - g_variant_get (reply, "(^a&s)", &names); - - for (guint i = 0; names[i] != NULL; i++) - { - Peer *peer; - - peer = g_slice_new (Peer); - peer->self = g_object_ref (self); - peer->name = g_strdup (names[i]); - - g_dbus_connection_call (bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionUnixProcessID", - g_variant_new ("(s)", names[i]), - G_VARIANT_TYPE ("(u)"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, - self->cancellable, - sysprof_proxy_source_get_pid_cb, - g_steal_pointer (&peer)); - } -} - -static void -sysprof_proxy_source_name_owner_changed_cb (GDBusConnection *connection, - const gchar *sender_name, - const gchar *object_path, - const gchar *interface_name, - const gchar *signal_name, - GVariant *params, - gpointer user_data) -{ - GWeakRef *wr = user_data; - g_autoptr(SysprofProxySource) self = NULL; - const gchar *name; - const gchar *old_name; - const gchar *new_name; - - g_assert (G_IS_DBUS_CONNECTION (connection)); - g_assert (params != NULL); - g_assert (g_variant_is_of_type (params, G_VARIANT_TYPE ("(sss)"))); - g_assert (wr != NULL); - - g_variant_get (params, "(&s&s&s)", &name, &old_name, &new_name); - - if (!(self = g_weak_ref_get (wr))) - return; - - if (self->bus_name != NULL && g_strcmp0 (name, self->bus_name) == 0) - sysprof_proxy_source_monitor (self, connection, new_name); -} - -static void -sysprof_proxy_source_get_bus_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - g_autoptr(SysprofProxySource) self = user_data; - g_autoptr(GDBusConnection) bus = NULL; - g_autoptr(GError) error = NULL; - - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - - if (!(bus = g_bus_get_finish (result, &error))) - { - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - sysprof_source_emit_failed (SYSPROF_SOURCE (self), error); - return; - } - - if (self->bus_name != NULL && g_dbus_is_name (self->bus_name)) - { - GWeakRef *wr; - - /* Try to monitor immediately */ - sysprof_proxy_source_monitor (self, bus, self->bus_name); - - /* Watch for changes in case the program isn't started yet and - * we want to monitor it after it is available. - */ - wr = g_slice_new0 (GWeakRef); - g_weak_ref_init (wr, self); - g_dbus_connection_signal_subscribe (bus, - NULL, - "org.freedesktop.DBus", - "NameOwnerChanged", - NULL, - NULL, - 0, - sysprof_proxy_source_name_owner_changed_cb, - g_steal_pointer (&wr), - (GDestroyNotify)_g_weak_ref_free); - } - - if (self->pids->len > 0) - { - /* We need to query the processes that have been spawned to see - * if they have our proxy address associated with them. But first, - * we need to find what pids own what connection. - */ - g_dbus_connection_call (bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ListNames", - g_variant_new ("()"), - G_VARIANT_TYPE ("(as)"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, - self->cancellable, - sysprof_proxy_source_list_names_cb, - g_object_ref (self)); - return; - } -} - -static void -sysprof_proxy_source_start (SysprofSource *source) -{ - SysprofProxySource *self = (SysprofProxySource *)source; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - - self->has_started = TRUE; - - g_bus_get (self->bus_type, - self->cancellable, - sysprof_proxy_source_get_bus_cb, - g_object_ref (self)); -} - -static void -sysprof_proxy_source_cat (SysprofProxySource *self, - SysprofCaptureReader *reader) -{ - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (reader != NULL); - - if (self->writer != NULL && - !sysprof_capture_writer_cat (self->writer, reader)) - { - int errsv = errno; - g_warning ("Failed to join reader: %s", g_strerror (errsv)); - } -} - -static void -sysprof_proxy_source_complete_monitor (SysprofProxySource *self, - Monitor *monitor) -{ - g_autoptr(SysprofCaptureReader) reader = NULL; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (monitor != NULL); - g_assert (monitor->self == self); - - if (!(reader = sysprof_capture_reader_new_from_fd (steal_fd (&monitor->fd)))) - { - int errsv = errno; - g_warning ("Failed to load reader from peer FD: %s", g_strerror (errsv)); - } - else - sysprof_proxy_source_cat (self, reader); -} - -static void -sysprof_proxy_source_stop_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - GDBusConnection *bus = (GDBusConnection *)object; - g_autoptr(Monitor) monitor = user_data; - g_autoptr(GVariant) reply = NULL; - g_autoptr(GError) error = NULL; - SysprofProxySource *self; - - g_assert (G_IS_DBUS_CONNECTION (bus)); - g_assert (G_IS_ASYNC_RESULT (result)); - g_assert (monitor != NULL); - - self = monitor->self; - reply = g_dbus_connection_call_finish (bus, result, &error); - monitor->needs_stop = FALSE; - - sysprof_proxy_source_complete_monitor (self, monitor); - - self->stopping_count--; - - if (self->stopping_count == 0) - sysprof_source_emit_finished (SYSPROF_SOURCE (self)); -} - -static void -sysprof_proxy_source_stop (SysprofSource *source) -{ - SysprofProxySource *self = (SysprofProxySource *)source; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - - g_cancellable_cancel (self->cancellable); - - for (guint i = 0; i < self->monitors->len; i++) - { - g_autoptr(Monitor) monitor = g_ptr_array_index (self->monitors, i); - - g_ptr_array_index (self->monitors, i) = NULL; - - if (monitor->needs_stop) - { - self->stopping_count++; - g_dbus_connection_call (monitor->bus, - monitor->name, - monitor->object_path, - "org.gnome.Sysprof3.Profiler", - "Stop", - g_variant_new ("()"), - G_VARIANT_TYPE ("()"), - G_DBUS_CALL_FLAGS_NO_AUTO_START, - -1, - NULL, - sysprof_proxy_source_stop_cb, - monitor); - monitor = NULL; /* stolen */ - } - else - { - sysprof_proxy_source_complete_monitor (self, monitor); - } - } - - if (self->stopping_count == 0) - sysprof_source_emit_finished (source); -} - -static void -sysprof_proxy_source_add_pid (SysprofSource *source, - GPid pid) -{ - SysprofProxySource *self = (SysprofProxySource *)source; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (pid > 0); - - if (!self->has_started) - self->is_whole_system = FALSE; - - g_array_append_val (self->pids, pid); -} - -static void -sysprof_proxy_source_serialize (SysprofSource *source, - GKeyFile *keyfile, - const gchar *group) -{ - SysprofProxySource *self = (SysprofProxySource *)source; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (keyfile != NULL); - g_assert (group != NULL); - - g_key_file_set_string (keyfile, group, "bus-name", self->bus_name ?: ""); - g_key_file_set_string (keyfile, group, "object-path", self->object_path ?: ""); - g_key_file_set_integer (keyfile, group, "bus-type", self->bus_type); -} - -static void -sysprof_proxy_source_deserialize (SysprofSource *source, - GKeyFile *keyfile, - const gchar *group) -{ - SysprofProxySource *self = (SysprofProxySource *)source; - gint bus_type; - - g_assert (SYSPROF_IS_PROXY_SOURCE (self)); - g_assert (keyfile != NULL); - g_assert (group != NULL); - - g_clear_pointer (&self->bus_name, g_free); - g_clear_pointer (&self->object_path, g_free); - - self->bus_name = g_key_file_get_string (keyfile, group, "bus-name", NULL); - self->object_path = g_key_file_get_string (keyfile, group, "object-path", NULL); - - bus_type = g_key_file_get_integer (keyfile, group, "bus-type", NULL); - if (bus_type == G_BUS_TYPE_SESSION || bus_type == G_BUS_TYPE_SYSTEM) - self->bus_type = bus_type; -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->add_pid = sysprof_proxy_source_add_pid; - iface->prepare = sysprof_proxy_source_prepare; - iface->set_writer = sysprof_proxy_source_set_writer; - iface->get_is_ready = sysprof_proxy_source_get_is_ready; - iface->stop = sysprof_proxy_source_stop; - iface->start = sysprof_proxy_source_start; - iface->serialize = sysprof_proxy_source_serialize; - iface->deserialize = sysprof_proxy_source_deserialize; -} - -G_DEFINE_TYPE_WITH_CODE (SysprofProxySource, sysprof_proxy_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -sysprof_proxy_source_finalize (GObject *object) -{ - SysprofProxySource *self = (SysprofProxySource *)object; - - g_clear_pointer (&self->monitors, g_ptr_array_unref); - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - g_clear_pointer (&self->bus_name, g_free); - g_clear_pointer (&self->object_path, g_free); - g_clear_pointer (&self->pids, g_array_unref); - g_clear_object (&self->cancellable); - - G_OBJECT_CLASS (sysprof_proxy_source_parent_class)->finalize (object); -} - -static void -sysprof_proxy_source_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofProxySource *self = SYSPROF_PROXY_SOURCE (object); - - switch (prop_id) - { - case PROP_BUS_TYPE: - g_value_set_enum (value, self->bus_type); - break; - - case PROP_BUS_NAME: - g_value_set_string (value, self->bus_name); - break; - - case PROP_OBJECT_PATH: - g_value_set_string (value, self->object_path); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_proxy_source_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofProxySource *self = SYSPROF_PROXY_SOURCE (object); - - switch (prop_id) - { - case PROP_BUS_TYPE: - self->bus_type = g_value_get_enum (value); - break; - - case PROP_BUS_NAME: - g_free (self->bus_name); - self->bus_name = g_value_dup_string (value); - break; - - case PROP_OBJECT_PATH: - g_free (self->object_path); - self->object_path = g_value_dup_string (value); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_proxy_source_class_init (SysprofProxySourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_proxy_source_finalize; - object_class->get_property = sysprof_proxy_source_get_property; - object_class->set_property = sysprof_proxy_source_set_property; - - properties [PROP_BUS_TYPE] = - g_param_spec_enum ("bus-type", NULL, NULL, - G_TYPE_BUS_TYPE, - G_BUS_TYPE_SESSION, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_BUS_NAME] = - g_param_spec_string ("bus-name", NULL, NULL, - NULL, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - properties [PROP_OBJECT_PATH] = - g_param_spec_string ("object-path", NULL, NULL, - NULL, - (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_proxy_source_init (SysprofProxySource *self) -{ - self->cancellable = g_cancellable_new (); - self->pids = g_array_new (FALSE, FALSE, sizeof (GPid)); - self->monitors = g_ptr_array_new_with_free_func ((GDestroyNotify) monitor_free); - self->is_whole_system = TRUE; - self->bus_type = G_BUS_TYPE_SESSION; -} - -SysprofSource * -sysprof_proxy_source_new (GBusType bus_type, - const gchar *bus_name, - const gchar *object_path) -{ - SysprofProxySource *self; - - g_return_val_if_fail (bus_type == G_BUS_TYPE_SESSION || bus_type == G_BUS_TYPE_SYSTEM, NULL); - g_return_val_if_fail (bus_name != NULL, NULL); - g_return_val_if_fail (object_path != NULL, NULL); - - if (bus_name && !*bus_name) - bus_name = NULL; - - if (object_path && !*object_path) - object_path = NULL; - - self = g_object_new (SYSPROF_TYPE_PROXY_SOURCE, - "bus-type", bus_type, - "bus-name", bus_name, - "object-path", object_path, - NULL); - - return SYSPROF_SOURCE (g_steal_pointer (&self)); -} diff --git a/src/libsysprof/sysprof-recording-private.h b/src/libsysprof/sysprof-recording-private.h new file mode 100644 index 00000000..4fbbbe44 --- /dev/null +++ b/src/libsysprof/sysprof-recording-private.h @@ -0,0 +1,55 @@ +/* sysprof-recording-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-instrument.h" +#include "sysprof-recording.h" +#include "sysprof-spawnable.h" + +G_BEGIN_DECLS + +SysprofRecording *_sysprof_recording_new (SysprofCaptureWriter *writer, + SysprofSpawnable *spawnable, + SysprofInstrument **instruments, + guint n_instruments); +void _sysprof_recording_start (SysprofRecording *self); +SysprofCaptureWriter *_sysprof_recording_writer (SysprofRecording *self); +SysprofSpawnable *_sysprof_recording_get_spawnable (SysprofRecording *self); +DexFuture *_sysprof_recording_add_file (SysprofRecording *self, + const char *path, + gboolean compress); +void _sysprof_recording_add_file_data (SysprofRecording *self, + const char *path, + const char *contents, + gssize length, + gboolean compress); +void _sysprof_recording_diagnostic (SysprofRecording *self, + const char *domain, + const char *format, + ...) G_GNUC_PRINTF (3, 4); +void _sysprof_recording_error (SysprofRecording *self, + const char *domain, + const char *format, + ...) G_GNUC_PRINTF (3, 4); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-recording.c b/src/libsysprof/sysprof-recording.c new file mode 100644 index 00000000..36c882a9 --- /dev/null +++ b/src/libsysprof/sysprof-recording.c @@ -0,0 +1,950 @@ +/* sysprof-recording.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include + +#include "sysprof-diagnostic-private.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +typedef enum _SysprofRecordingCommand +{ + SYSPROF_RECORDING_COMMAND_STOP = 1, +} SysprofRecordingCommand; + +struct _SysprofRecording +{ + GObject parent_instance; + + /* Used to calculate the duration of the recording */ + gint64 start_time; + gint64 end_time; + + /* Used to calculate event count */ + SysprofCaptureStat stat; + + /* Diagnostics that may be added by instruments during the recording. + * Some may be fatal, meaning that they stop the recording when the + * diagnostic is submitted. That can happen in situations like + * miss-configuration or failed authorization. + */ + GListStore *diagnostics; + + /* If we are spawning a process as part of this recording, this + * is the SysprofSpawnable used to spawn the process. + */ + SysprofSpawnable *spawnable; + + /* This is where all of the instruments will write to. They are + * expected to do this from the main-thread only. To work from + * additional threads they need to proxy that state to the + * main thread for writing. + */ + SysprofCaptureWriter *writer; + + /* An array of SysprofInstrument that are part of this recording */ + GPtrArray *instruments; + + /* A DexFiber that will complete when the recording has finished, + * been stopped, or failed. + */ + DexFuture *fiber; + + /* The channel is used ot send state change messages to the fiber + * from outside of the fiber. + */ + DexChannel *channel; + + /* The process we have spawned, if any */ + GSubprocess *subprocess; +}; + +enum { + PROP_0, + PROP_DURATION, + PROP_EVENT_COUNT, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofRecording, sysprof_recording, G_TYPE_OBJECT) + +static GParamSpec *properties[N_PROPS]; + +static DexFuture * +_sysprof_recording_spawn (SysprofSpawnable *spawnable, + GSubprocess **subprocess) +{ + g_autoptr(GError) error = NULL; + DexFuture *ret; + + g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); + + if (!(*subprocess = sysprof_spawnable_spawn (spawnable, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + ret = dex_subprocess_wait_check (*subprocess); + dex_async_pair_set_cancel_on_discard (DEX_ASYNC_PAIR (ret), FALSE); + return ret; +} + +static inline void +add_metadata (SysprofRecording *self, + const char *id, + const char *value) +{ + if (value == NULL) + return; + + sysprof_capture_writer_add_metadata (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, -1, id, value, -1); +} + +static inline void +add_metadata_int (SysprofRecording *self, + const char *id, + int value) +{ + char str[16]; + g_snprintf (str, sizeof str, "%d", value); + sysprof_capture_writer_add_metadata (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, -1, id, str, -1); +} + +static inline void +add_metadata_int64 (SysprofRecording *self, + const char *id, + gint64 value) +{ + char str[32]; + g_snprintf (str, sizeof str, "%"G_GINT64_FORMAT, value); + sysprof_capture_writer_add_metadata (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, -1, id, str, -1); +} + +static DexFuture * +sysprof_recording_fiber (gpointer user_data) +{ + SysprofRecording *self = user_data; + g_autoptr(GCancellable) cancellable = NULL; + g_autoptr(DexFuture) record = NULL; + g_autoptr(DexFuture) monitor = NULL; + g_autoptr(DexFuture) message = NULL; + g_autoptr(GError) error = NULL; + struct utsname uts; + struct sysinfo si; + gint64 begin_time; + gint64 end_time; + char hostname[64] = {0}; + + g_assert (SYSPROF_IS_RECORDING (self)); + + cancellable = g_cancellable_new (); + + /* First ensure that all our required policy have been acquired on + * the bus so that we don't need to individually acquire them from + * each of the instruments after the recording starts. + */ + if (!dex_await (_sysprof_instruments_acquire_policy (self->instruments, self), &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* Now allow instruments to prepare for the recording */ + if (!dex_await (_sysprof_instruments_prepare (self->instruments, self), &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* Ask instruments to start recording and stop if cancelled. */ + record = _sysprof_instruments_record (self->instruments, self, cancellable); + + /* Now take our begin time now that all instruments are notified */ + begin_time = SYSPROF_CAPTURE_CURRENT_TIME; + + g_assert (self->subprocess == NULL); + + /* If we need to spawn a subprocess, do it now */ + if (self->spawnable != NULL) + monitor = _sysprof_recording_spawn (self->spawnable, &self->subprocess); + else + monitor = dex_future_new_infinite (); + + /* Track various app metadata */ + add_metadata (self, "org.gnome.sysprof.app-id", APP_ID_S); + add_metadata (self, "org.gnome.sysprof.version", PACKAGE_VERSION); + + /* Include some host/kernel/arch information */ + add_metadata_int (self, "n-cpu", g_get_num_processors ()); + add_metadata_int (self, "page-size", sysprof_getpagesize ()); + add_metadata_int (self, "buffer-size", sysprof_capture_writer_get_buffer_size (self->writer)); + if (uname (&uts) == 0) + { + add_metadata (self, "uname.sysname", uts.sysname); + add_metadata (self, "uname.release", uts.release); + add_metadata (self, "uname.version", uts.version); + add_metadata (self, "uname.machine", uts.machine); + } + + if (gethostname (hostname, sizeof hostname-1) == 0) + add_metadata (self, "hostname", hostname); + + /* More system information via sysinfo */ + if (sysinfo (&si) == 0) + { + add_metadata_int64 (self, "sysinfo.uptime", si.uptime); + add_metadata_int64 (self, "sysinfo.totalram", si.totalram); + add_metadata_int64 (self, "sysinfo.freeram", si.freeram); + add_metadata_int64 (self, "sysinfo.sharedram", si.sharedram); + add_metadata_int64 (self, "sysinfo.bufferram", si.bufferram); + add_metadata_int64 (self, "sysinfo.totalswap", si.totalswap); + add_metadata_int64 (self, "sysinfo.freeswap", si.freeswap); + add_metadata_int64 (self, "sysinfo.procs", si.procs); + add_metadata_int64 (self, "sysinfo.totalhigh", si.totalhigh); + add_metadata_int64 (self, "sysinfo.freehigh", si.freehigh); + add_metadata_int64 (self, "sysinfo.mem_unit", si.mem_unit); + } + + /* Some environment variables/info for correlating */ + add_metadata (self, "USER", g_get_user_name ()); + add_metadata (self, "DISPLAY", g_getenv ("DISPLAY")); + add_metadata (self, "WAYLAND_DISPLAY", g_getenv ("WAYLAND_DISPLAY")); + add_metadata (self, "DESKTOP_SESSION", g_getenv ("DESKTOP_SESSION")); + add_metadata (self, "HOSTTYPE", g_getenv ("HOSTTYPE")); + add_metadata (self, "OSTYPE", g_getenv ("OSTYPE")); + + /* Log information about the spawning process */ + if (self->spawnable != NULL) + { + const char * const *argv = sysprof_spawnable_get_argv (self->spawnable); + const char * const *env = sysprof_spawnable_get_environ (self->spawnable); + const char *cwd = sysprof_spawnable_get_cwd (self->spawnable); + + if (cwd) + add_metadata (self, "spawnable.cwd", cwd); + + if (env != NULL) + { + g_autoptr(GString) str = g_string_new (NULL); + + for (guint e = 0; env[e]; e++) + { + g_autofree char *quoted = g_shell_quote (env[e]); + + g_string_append (str, quoted); + g_string_append_c (str, ' '); + } + + add_metadata (self, "spawnable.environ", str->str); + } + + if (argv != NULL) + { + g_autoptr(GString) str = g_string_new (NULL); + + for (guint a = 0; argv[a]; a++) + { + g_autofree char *quoted = g_shell_quote (argv[a]); + + g_string_append (str, quoted); + g_string_append_c (str, ' '); + } + + add_metadata (self, "spawnable.argv", str->str); + } + } + + /* Save a copy of os-release for troubleshooting */ + dex_await (_sysprof_recording_add_file (self, "/etc/os-release", FALSE), NULL); + + self->start_time = g_get_monotonic_time (); + + /* Queue receive of first message */ + message = dex_channel_receive (self->channel); + + /* Wait for messages on our channel or the recording to complete */ + for (;;) + { + g_autoptr(DexFuture) duration = dex_timeout_new_seconds (1); + + g_debug ("Recording loop iteration"); + + /* Wait for either recording of all instruments to complete or a + * message from our channel with what to do next. + */ + if (!dex_await (dex_future_first (dex_ref (record), + dex_ref (message), + dex_ref (monitor), + dex_ref (duration), + NULL), + &error) && + !g_error_matches (error, DEX_ERROR, DEX_ERROR_TIMED_OUT)) + goto stop_recording; + + /* Clear any ignored error */ + g_clear_error (&error); + + /* If record is not pending, then everything resolved/rejected */ + if (dex_future_get_status (record) != DEX_FUTURE_STATUS_PENDING || + dex_future_get_status (monitor) != DEX_FUTURE_STATUS_PENDING) + goto stop_recording; + + /* If message resolved, then we got a command to process */ + if (dex_future_get_status (message) == DEX_FUTURE_STATUS_RESOLVED) + { + SysprofRecordingCommand command = dex_await_uint (dex_ref (message), NULL); + + switch (command) + { + case SYSPROF_RECORDING_COMMAND_STOP: + g_debug ("Recording received stop command"); + goto stop_recording; + + default: + break; + } + + /* Queue receive of next message */ + dex_clear (&message); + message = dex_channel_receive (self->channel); + } + + /* Update duration each pass through the loop */ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); + + /* Update event count each pass through the loop */ + sysprof_capture_writer_stat (self->writer, &self->stat); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EVENT_COUNT]); + } + +stop_recording: + g_debug ("Stopping recording"); + + end_time = SYSPROF_CAPTURE_CURRENT_TIME; + + self->end_time = g_get_monotonic_time (); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DURATION]); + + /* Signal cancellable so that anything lingering has a chance to be + * cleaned up, cascading into other subsystems. + */ + g_cancellable_cancel (cancellable); + + /* But we must still wait for instruments to respond to + * the cancellation and clean up before we can move onto + * the augmentation phase. + */ + dex_await (dex_ref (record), NULL); + + /* Let instruments augment the capture. Some instruments may include + * extra information about the capture such as symbol names and their + * address ranges per-process. + */ + dex_await (_sysprof_instruments_augment (self->instruments, self), NULL); + + /* Update start/end times to be the "running time" */ + _sysprof_capture_writer_set_time_range (self->writer, begin_time, end_time); + + /* Clear buffers and ensure the disk layer has access to them */ + sysprof_capture_writer_flush (self->writer); + + /* Ignore error types we use to bail out of loops */ + if (error != NULL && + !g_error_matches (error, DEX_ERROR, DEX_ERROR_TIMED_OUT) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + return dex_future_new_for_boolean (TRUE); +} + +static void +sysprof_recording_finalize (GObject *object) +{ + SysprofRecording *self = (SysprofRecording *)object; + + if (self->channel) + { + dex_channel_close_send (self->channel); + dex_clear (&self->channel); + } + + g_clear_pointer (&self->writer, sysprof_capture_writer_unref); + g_clear_pointer (&self->instruments, g_ptr_array_unref); + g_clear_object (&self->spawnable); + g_clear_object (&self->diagnostics); + g_clear_object (&self->subprocess); + dex_clear (&self->fiber); + + G_OBJECT_CLASS (sysprof_recording_parent_class)->finalize (object); +} + +static void +sysprof_recording_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofRecording *self = SYSPROF_RECORDING (object); + + switch (prop_id) + { + case PROP_DURATION: + g_value_set_int64 (value, sysprof_recording_get_duration (self)); + break; + + case PROP_EVENT_COUNT: + g_value_set_int64 (value, sysprof_recording_get_event_count (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_recording_class_init (SysprofRecordingClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_recording_finalize; + object_class->get_property = sysprof_recording_get_property; + + properties [PROP_DURATION] = + g_param_spec_int64 ("duration", NULL, NULL, + 0, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_EVENT_COUNT] = + g_param_spec_int64 ("event-count", NULL, NULL, + 0, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_recording_init (SysprofRecording *self) +{ + self->channel = dex_channel_new (0); + self->instruments = g_ptr_array_new_with_free_func (g_object_unref); + self->diagnostics = g_list_store_new (SYSPROF_TYPE_DIAGNOSTIC); +} + +SysprofRecording * +_sysprof_recording_new (SysprofCaptureWriter *writer, + SysprofSpawnable *spawnable, + SysprofInstrument **instruments, + guint n_instruments) +{ + SysprofRecording *self; + + g_return_val_if_fail (writer != NULL, NULL); + + self = g_object_new (SYSPROF_TYPE_RECORDING, NULL); + self->writer = sysprof_capture_writer_ref (writer); + + g_set_object (&self->spawnable, spawnable); + + for (guint i = 0; i < n_instruments; i++) + g_ptr_array_add (self->instruments, g_object_ref (instruments[i])); + + return self; +} + +void +_sysprof_recording_start (SysprofRecording *self) +{ + + g_return_if_fail (SYSPROF_IS_RECORDING (self)); + g_return_if_fail (self->fiber == NULL); + + self->fiber = dex_scheduler_spawn (NULL, 0, + sysprof_recording_fiber, + g_object_ref (self), + g_object_unref); +} + +void +sysprof_recording_stop_async (SysprofRecording *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(DexAsyncResult) result = NULL; + + g_return_if_fail (SYSPROF_IS_RECORDING (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + result = dex_async_result_new (self, cancellable, callback, user_data); + dex_async_result_await (result, + dex_channel_send (self->channel, + dex_future_new_for_uint (SYSPROF_RECORDING_COMMAND_STOP))); +} + +gboolean +sysprof_recording_stop_finish (SysprofRecording *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), FALSE); + g_return_val_if_fail (DEX_IS_ASYNC_RESULT (result), FALSE); + + return dex_async_result_propagate_boolean (DEX_ASYNC_RESULT (result), error); +} + +void +sysprof_recording_wait_async (SysprofRecording *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(DexAsyncResult) result = NULL; + + g_return_if_fail (SYSPROF_IS_RECORDING (self)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + result = dex_async_result_new (self, cancellable, callback, user_data); + dex_async_result_await (result, dex_ref (self->fiber)); +} + +gboolean +sysprof_recording_wait_finish (SysprofRecording *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), FALSE); + g_return_val_if_fail (DEX_IS_ASYNC_RESULT (result), FALSE); + + return dex_async_result_propagate_boolean (DEX_ASYNC_RESULT (result), error); +} + +SysprofSpawnable * +_sysprof_recording_get_spawnable (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + + return self->spawnable; +} + +SysprofCaptureWriter * +_sysprof_recording_writer (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + + return self->writer; +} + +typedef struct _AddFile +{ + SysprofCaptureWriter *writer; + char *path; + guint compress : 1; +} AddFile; + +static void +add_file_free (AddFile *add_file) +{ + g_clear_pointer (&add_file->writer, sysprof_capture_writer_unref); + g_clear_pointer (&add_file->path, g_free); + g_free (add_file); +} + +static DexFuture * +sysprof_recording_add_file_fiber (gpointer user_data) +{ + AddFile *add_file = user_data; + g_autoptr(GInputStream) input = NULL; + g_autoptr(GOutputStream) memory_stream = NULL; + g_autoptr(GOutputStream) zlib_stream = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GBytes) bytes = NULL; + g_autoptr(GFile) proc = NULL; + g_autoptr(GFile) file = NULL; + g_autofree char *dest_path = NULL; + const guint8 *data = NULL; + GOutputStream *output; + gsize len; + + g_assert (add_file != NULL); + g_assert (add_file->path != NULL); + g_assert (add_file->writer != NULL); + + /* If we are compressing the file, store it as ".gz" in the capture + * so that the reader knows to decompress it automatically. + */ + dest_path = add_file->compress ? g_strdup_printf ("%s.gz", add_file->path) + : g_strdup (add_file->path); + + file = g_file_new_for_path (add_file->path); + proc = g_file_new_for_path ("/proc"); + + /* If the file has a prefix of `/proc/` then we need to request the + * file from sysprofd as our user is not guaranteed to be able to + * read the file. Even if we can open the file, we may get data that + * has been redacted (such as /proc/kallsyms). + * + * With `/proc/kallsyms` it's even worse in that if we get an FD back + * from sysprofd and read it from our process, it *too* will be redacted. + * That leaves us with the only option of letting sysprofd read the file + * and transfer the contents to our process over D-Bus. + * + * We use g_file_has_prefix() for this as it will canonicalize the paths + * of #GFile rather than us having to be careful here. + */ + if (g_file_has_prefix (file, proc)) + { + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GVariant) reply = NULL; + g_autoptr(GBytes) input_bytes = NULL; + + if (!(connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + if (!(reply = dex_await_variant (dex_dbus_connection_call (connection, + "org.gnome.Sysprof3", + "/org/gnome/Sysprof3", + "org.gnome.Sysprof3.Service", + "GetProcFile", + g_variant_new ("(^ay)", g_file_get_path (file)), + G_VARIANT_TYPE ("(ay)"), + G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, + G_MAXINT), + &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + input_bytes = g_variant_get_data_as_bytes (reply); + input = g_memory_input_stream_new_from_bytes (input_bytes); + } + else + { + if (!(input = dex_await_object (dex_file_read (file, 0), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + } + + g_assert (input != NULL); + g_assert (G_IS_INPUT_STREAM (input)); + + output = memory_stream = g_memory_output_stream_new_resizable (); + + if (add_file->compress) + { + g_autoptr(GZlibCompressor) compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, 6); + output = zlib_stream = g_converter_output_stream_new (memory_stream, G_CONVERTER (compressor)); + } + + g_assert (output != NULL); + g_assert (G_IS_OUTPUT_STREAM (output)); + g_assert (G_IS_MEMORY_OUTPUT_STREAM (output) || G_IS_CONVERTER_OUTPUT_STREAM (output)); + g_assert (G_IS_MEMORY_OUTPUT_STREAM (memory_stream)); + g_assert (!zlib_stream || G_IS_CONVERTER_OUTPUT_STREAM (zlib_stream)); + + if (!dex_await (dex_output_stream_splice (output, + input, + (G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET), + 0), + &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (memory_stream)); + data = g_bytes_get_data (bytes, &len); + + while (len > 0) + { + gsize to_write = MIN (len, ((4096*8)-sizeof (SysprofCaptureFileChunk))); + + if (!sysprof_capture_writer_add_file (add_file->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + dest_path, + to_write == len, + data, + to_write)) + break; + + len -= to_write; + data += to_write; + } + + return dex_future_new_for_boolean (TRUE); +} + +DexFuture * +_sysprof_recording_add_file (SysprofRecording *self, + const char *path, + gboolean compress) +{ + g_autoptr(GFile) file = NULL; + AddFile *add_file; + + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + g_return_val_if_fail (path != NULL, NULL); + + add_file = g_new0 (AddFile, 1); + add_file->writer = sysprof_capture_writer_ref (self->writer); + add_file->path = g_strdup (path); + add_file->compress = !!compress; + + return dex_scheduler_spawn (NULL, 0, + sysprof_recording_add_file_fiber, + g_steal_pointer (&add_file), + (GDestroyNotify)add_file_free); +} + +static char * +do_compress (const char *data, + gsize length, + gsize *out_length) +{ + g_autoptr(GZlibCompressor) compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, 6); + g_autofree char *compressed = g_malloc (length); + GConverterResult res; + gsize n_read; + gsize n_written; + + *out_length = 0; + + res = g_converter_convert (G_CONVERTER (compressor), data, length, compressed, length, + G_CONVERTER_INPUT_AT_END | G_CONVERTER_FLUSH, + &n_read, &n_written, NULL); + + if (res == G_CONVERTER_FINISHED) + { + *out_length = n_written; + return g_steal_pointer (&compressed); + } + + return NULL; +} + +void +_sysprof_recording_add_file_data (SysprofRecording *self, + const char *path, + const char *contents, + gssize length, + gboolean compress) +{ + g_autofree char *compress_filename = NULL; + g_autofree char *compress_bytes = NULL; + gsize compress_len = 0; + + g_return_if_fail (SYSPROF_IS_RECORDING (self)); + g_return_if_fail (path != NULL); + g_return_if_fail (contents != NULL); + + if (length < 0) + length = strlen (contents); + + if (length == 0) + compress = FALSE; + + if (compress) + { + compress_bytes = do_compress (contents, length, &compress_len); + + if (compress_bytes) + { + compress_filename = g_strdup_printf ("%s.gz", path); + path = compress_filename; + contents = compress_bytes; + length = compress_len; + } + } + + while (length > 0) + { + gsize to_write = MIN (length, ((4096*8)-sizeof (SysprofCaptureFileChunk))); + + if (!sysprof_capture_writer_add_file (self->writer, + SYSPROF_CAPTURE_CURRENT_TIME, + -1, + -1, + path, + to_write == length, + (const guint8 *)contents, + to_write)) + break; + + length -= to_write; + contents += to_write; + } +} + +static void +_sysprof_recording_message_internal (SysprofRecording *self, + const char *domain, + const char *format, + va_list *args, + gboolean fatal) +{ + g_autoptr(SysprofDiagnostic) diagnostic = NULL; + + g_assert (SYSPROF_IS_RECORDING (self)); + g_assert (domain != NULL); + g_assert (format != NULL); + g_assert (args != NULL); + + diagnostic = _sysprof_diagnostic_new (g_strdup (domain), + g_strdup_vprintf (format, *args), + fatal); + + g_list_store_append (self->diagnostics, diagnostic); + + if (fatal) + sysprof_recording_stop_async (self, NULL, NULL, NULL); +} + +void +_sysprof_recording_diagnostic (SysprofRecording *self, + const char *domain, + const char *format, + ...) +{ + va_list args; + + va_start (args, format); + _sysprof_recording_message_internal (self, domain, format, &args, FALSE); + va_end (args); +} + +void +_sysprof_recording_error (SysprofRecording *self, + const char *domain, + const char *format, + ...) +{ + va_list args; + + va_start (args, format); + _sysprof_recording_message_internal (self, domain, format, &args, TRUE); + va_end (args); +} + +/** + * sysprof_recording_list_diagnostics: + * @self: a #SysprofRecording + * + * Gets the diagnostics for the recording which may be updated as + * instruments discover issues with the recording or configuration. + * + * Returns: (transfer full): a #GListModel of #SysprofDiagnostic + */ +GListModel * +sysprof_recording_list_diagnostics (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + + return g_object_ref (G_LIST_MODEL (self->diagnostics)); +} + +/** + * sysprof_recording_get_duration: + * @self: a #SysprofRecording + * + * Gets the recording duration in microseconds, which is the same + * precision used by g_get_monotonic_time(). Use %G_USEC_PER_SEC to + * get the time in seconds. + * + * Returns: the duration of the recording, or 0 + */ +gint64 +sysprof_recording_get_duration (SysprofRecording *self) +{ + gint64 start_time; + gint64 end_time; + + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), 0); + + if (!(start_time = self->start_time)) + return 0; + + if (!(end_time = self->end_time)) + end_time = g_get_monotonic_time (); + + return end_time - start_time; +} + +gint64 +sysprof_recording_get_event_count (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), 0); + + return self->stat.frame_count[SYSPROF_CAPTURE_FRAME_SAMPLE] + + self->stat.frame_count[SYSPROF_CAPTURE_FRAME_ALLOCATION] + + self->stat.frame_count[SYSPROF_CAPTURE_FRAME_FORK] + + self->stat.frame_count[SYSPROF_CAPTURE_FRAME_EXIT] + + self->stat.frame_count[SYSPROF_CAPTURE_FRAME_CTRSET] + + self->stat.frame_count[SYSPROF_CAPTURE_FRAME_MARK] + + self->stat.frame_count[SYSPROF_CAPTURE_FRAME_LOG]; +} + +/** + * sysprof_recording_create_reader: + * @self: a #SysprofRecording + * + * Creates a new reader for the recording's writer. + * + * Returns: (transfer full): a #SysprofCaptureReader + */ +SysprofCaptureReader * +sysprof_recording_create_reader (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + + return sysprof_capture_writer_create_reader (self->writer); +} + +/** + * sysprof_recording_dup_fd: + * @self: a #SysprofRecording + * + * Duplicates the FD that is being used for writing the recording. This can + * be useful if you want to open the recording using a + * #SysprofDocumentLoader. + * + * Returns: a FD that the caller should `close()` when no longer in use. + */ +int +sysprof_recording_dup_fd (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), -1); + + return _sysprof_capture_writer_dup_fd (self->writer); +} + +/** + * sysprof_recording_get_subprocess: + * @self: a #SysprofRecording + * + * Gets the #GSubprocess if one was spawned. + * + * Returns: (transfer none) (nullable): a #GSubprocess or %NULL + */ +GSubprocess * +sysprof_recording_get_subprocess (SysprofRecording *self) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (self), NULL); + + return self->subprocess; +} diff --git a/src/libsysprof/sysprof-recording.h b/src/libsysprof/sysprof-recording.h new file mode 100644 index 00000000..e8b491f7 --- /dev/null +++ b/src/libsysprof/sysprof-recording.h @@ -0,0 +1,65 @@ +/* sysprof-recording.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_RECORDING (sysprof_recording_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofRecording, sysprof_recording, SYSPROF, RECORDING, GObject) + +SYSPROF_AVAILABLE_IN_ALL +GListModel *sysprof_recording_list_diagnostics (SysprofRecording *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_recording_get_duration (SysprofRecording *self); +SYSPROF_AVAILABLE_IN_ALL +gint64 sysprof_recording_get_event_count (SysprofRecording *self); +SYSPROF_AVAILABLE_IN_ALL +GSubprocess *sysprof_recording_get_subprocess (SysprofRecording *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_recording_wait_async (SysprofRecording *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_recording_wait_finish (SysprofRecording *self, + GAsyncResult *result, + GError **error); +SYSPROF_AVAILABLE_IN_ALL +SysprofCaptureReader *sysprof_recording_create_reader (SysprofRecording *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_recording_dup_fd (SysprofRecording *self) G_GNUC_WARN_UNUSED_RESULT; +SYSPROF_AVAILABLE_IN_ALL +void sysprof_recording_stop_async (SysprofRecording *self, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_recording_stop_finish (SysprofRecording *self, + GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-sampler.c b/src/libsysprof/sysprof-sampler.c new file mode 100644 index 00000000..e8ecd1b7 --- /dev/null +++ b/src/libsysprof/sysprof-sampler.c @@ -0,0 +1,470 @@ +/* sysprof-sampler.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-instrument-private.h" +#include "sysprof-perf-event-stream-private.h" +#include "sysprof-recording-private.h" +#include "sysprof-sampler.h" + +#define N_WAKEUP_EVENTS 149 + +struct _SysprofSampler +{ + SysprofInstrument parent_instance; + GPtrArray *perf_event_streams; +}; + +struct _SysprofSamplerClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofSampler, sysprof_sampler, SYSPROF_TYPE_INSTRUMENT) + +static char ** +sysprof_sampler_list_required_policy (SysprofInstrument *instrument) +{ + static const char *policy[] = {"org.gnome.sysprof3.profile", NULL}; + + return g_strdupv ((char **)policy); +} + +static inline void +realign (gsize *pos, + gsize align) +{ + *pos = (*pos + align - 1) & ~(align - 1); +} + +static void +sysprof_sampler_add_callback (SysprofCaptureWriter *writer, + int cpu, + const SysprofPerfEventCallchain *sample) +{ + const guint64 *ips; + guint64 trace[3]; + int n_ips; + + g_assert (writer != NULL); + g_assert (sample != NULL); + + ips = sample->ips; + n_ips = sample->n_ips; + + if (n_ips == 0) + { + if (sample->header.misc & PERF_RECORD_MISC_KERNEL) + { + trace[0] = PERF_CONTEXT_KERNEL; + trace[1] = sample->ip; + trace[2] = PERF_CONTEXT_USER; + + ips = trace; + n_ips = 3; + } + else + { + trace[0] = PERF_CONTEXT_USER; + trace[1] = sample->ip; + + ips = trace; + n_ips = 2; + } + } + + sysprof_capture_writer_add_sample (writer, + sample->time, + cpu, + sample->pid, + sample->tid, + ips, + n_ips); +} + +static void +sysprof_sampler_perf_event_stream_cb (const SysprofPerfEvent *event, + guint cpu, + gpointer user_data) +{ + SysprofCaptureWriter *writer = user_data; + gsize offset; + gint64 time; + + g_assert (writer != NULL); + g_assert (event != NULL); + + switch (event->header.type) + { + case PERF_RECORD_COMM: + offset = strlen (event->comm.comm) + 1; + realign (&offset, sizeof (guint64)); + offset += sizeof (GPid) + sizeof (GPid); + memcpy (&time, event->comm.comm + offset, sizeof time); + + if (event->comm.pid == event->comm.tid) + sysprof_capture_writer_add_process (writer, + time, + cpu, + event->comm.pid, + event->comm.comm); + + break; + + case PERF_RECORD_EXIT: + /* Ignore fork exits for now */ + if (event->exit.tid != event->exit.pid) + break; + + sysprof_capture_writer_add_exit (writer, + event->exit.time, + cpu, + event->exit.pid); + + break; + + case PERF_RECORD_FORK: + sysprof_capture_writer_add_fork (writer, + event->fork.time, + cpu, + event->fork.ptid, + event->fork.tid); + + /* + * TODO: We should add support for "follow fork" of the GPid if we are + * targetting it. + */ + + break; + + case PERF_RECORD_LOST: + break; + + case PERF_RECORD_MMAP: + offset = strlen (event->mmap.filename) + 1; + realign (&offset, sizeof (guint64)); + offset += sizeof (GPid) + sizeof (GPid); + memcpy (&time, event->mmap.filename + offset, sizeof time); + + sysprof_capture_writer_add_map (writer, + time, + cpu, + event->mmap.pid, + event->mmap.addr, + event->mmap.addr + event->mmap.len, + event->mmap.pgoff, + 0, + event->mmap.filename); + + break; + + case PERF_RECORD_READ: + break; + + case PERF_RECORD_SAMPLE: + sysprof_sampler_add_callback (writer, cpu, &event->callchain); + break; + + case PERF_RECORD_THROTTLE: + case PERF_RECORD_UNTHROTTLE: + default: + break; + } +} + +typedef struct _Prepare +{ + SysprofRecording *recording; + SysprofSampler *sampler; +} Prepare; + +static void +prepare_free (Prepare *prepare) +{ + g_clear_object (&prepare->recording); + g_clear_object (&prepare->sampler); + g_free (prepare); +} + +static DexFuture * +sysprof_sampler_prepare_fiber (gpointer user_data) +{ + Prepare *prepare = user_data; + SysprofCaptureWriter *writer; + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GPtrArray) futures = NULL; + g_autoptr(GError) error = NULL; + struct perf_event_attr attr = {0}; + guint n_cpu; + + g_assert (prepare != NULL); + g_assert (SYSPROF_IS_RECORDING (prepare->recording)); + g_assert (SYSPROF_IS_SAMPLER (prepare->sampler)); + + /* First thing we need to do is to ensure the consumer has + * access to kallsyms, which may be from a machine, or boot + * different than this boot (and therefore symbols exist in + * different locations). Embed the kallsyms, but gzip it as + * those files can be quite large. + */ + if (!dex_await (_sysprof_recording_add_file (prepare->recording, + "/proc/kallsyms", + TRUE), + &error)) + { + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Failed to record copy of “kallsyms” to capture: %s", + error->message); + g_clear_error (&error); + } + + /* Now create a SysprofPerfEventStream for every CPU on the + * system. Linux Perf will only let us create a stream for + * a single PID on all CPU, or all PID on a single CPU. So + * we create one per-CPU and stream those results into the + * capture file during recording. + * + * Previously, we supported recording a single process but + * that is more effort than it is worth, since virtually + * nobody uses Sysprof that way. + */ + n_cpu = g_get_num_processors (); + futures = g_ptr_array_new_with_free_func (dex_unref); + writer = _sysprof_recording_writer (prepare->recording); + + attr.sample_type = PERF_SAMPLE_IP + | PERF_SAMPLE_TID + | PERF_SAMPLE_IDENTIFIER + | PERF_SAMPLE_CALLCHAIN + | PERF_SAMPLE_TIME; + attr.wakeup_events = N_WAKEUP_EVENTS; + attr.disabled = TRUE; + attr.mmap = 1; + attr.comm = 1; + attr.task = 1; + attr.exclude_idle = 1; + attr.sample_id_all = 1; + +#ifdef HAVE_PERF_CLOCKID + attr.clockid = sysprof_clock; + attr.use_clockid = 1; +#endif + + attr.size = sizeof attr; + + attr.type = PERF_TYPE_HARDWARE; + attr.config = PERF_COUNT_HW_CPU_CYCLES; + attr.sample_period = 1200000; + + if (!(connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + /* Pipeline our request for n_cpu perf_event_open calls and then + * await them all to complete. + */ + for (guint i = 0; i < n_cpu; i++) + g_ptr_array_add (futures, + sysprof_perf_event_stream_new (connection, + &attr, + i, + -1, + 0, + sysprof_sampler_perf_event_stream_cb, + sysprof_capture_writer_ref (writer), + (GDestroyNotify)sysprof_capture_writer_unref)); + + if (!dex_await (dex_future_allv ((DexFuture **)futures->pdata, futures->len), &error)) + { + guint failed = 0; + + for (guint i = 0; i < futures->len; i++) + { + DexFuture *future = g_ptr_array_index (futures, i); + + if (dex_future_get_status (future) == DEX_FUTURE_STATUS_REJECTED) + { + g_autoptr(GError) future_error = NULL; + dex_future_get_value (future, &future_error); + _sysprof_recording_diagnostic (prepare->recording, + "Sampler", + "Failed to load Perf event stream for CPU %d: %s", + i, future_error->message); + failed++; + } + } + + if (failed == futures->len) + return dex_future_new_for_error (g_steal_pointer (&error)); + } + + /* Save each of the streams (currently corked), so that we can + * uncork them while recording. We already checked that all the + * futures have succeeded above, so dex_await_object() must + * always return an object for each sub-future. + */ + for (guint i = 0; i < futures->len; i++) + { + DexFuture *future = g_ptr_array_index (futures, i); + g_autoptr(SysprofPerfEventStream) stream = NULL; + g_autoptr(GError) stream_error = NULL; + + if ((stream = dex_await_object (dex_ref (future), &stream_error))) + g_ptr_array_add (prepare->sampler->perf_event_streams, g_steal_pointer (&stream)); + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_sampler_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofSampler *self = (SysprofSampler *)instrument; + Prepare *prepare; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + prepare = g_new0 (Prepare, 1); + prepare->recording = g_object_ref (recording); + prepare->sampler = g_object_ref (self); + + return dex_scheduler_spawn (NULL, 0, + sysprof_sampler_prepare_fiber, + prepare, + (GDestroyNotify)prepare_free); +} + +typedef struct _Record +{ + SysprofRecording *recording; + SysprofSampler *sampler; + DexFuture *cancellable; +} Record; + +static void +record_free (Record *record) +{ + g_clear_object (&record->recording); + g_clear_object (&record->sampler); + dex_clear (&record->cancellable); + g_free (record); +} + +static DexFuture * +sysprof_sampler_record_fiber (gpointer user_data) +{ + Record *record = user_data; + g_autoptr(GError) error = NULL; + + g_assert (record != NULL); + g_assert (SYSPROF_IS_SAMPLER (record->sampler)); + g_assert (SYSPROF_IS_RECORDING (record->recording)); + g_assert (DEX_IS_FUTURE (record->cancellable)); + + for (guint i = 0; i < record->sampler->perf_event_streams->len; i++) + { + SysprofPerfEventStream *stream = g_ptr_array_index (record->sampler->perf_event_streams, i); + + if (!sysprof_perf_event_stream_enable (stream, &error)) + g_debug ("%s", error->message); + else + g_debug ("Sampler %d enabled", i); + + g_clear_error (&error); + } + + if (!dex_await (dex_ref (record->cancellable), &error)) + g_debug ("Sampler shutting down for reason: %s", error->message); + + for (guint i = 0; i < record->sampler->perf_event_streams->len; i++) + { + SysprofPerfEventStream *stream = g_ptr_array_index (record->sampler->perf_event_streams, i); + + if (!sysprof_perf_event_stream_disable (stream, &error)) + g_debug ("%s", error->message); + else + g_debug ("Sampler %d disabled", i); + + g_clear_error (&error); + } + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_sampler_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ + SysprofSampler *self = (SysprofSampler *)instrument; + Record *record; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + record = g_new0 (Record, 1); + record->recording = g_object_ref (recording); + record->sampler = g_object_ref (self); + record->cancellable = dex_cancellable_new_from_cancellable (cancellable); + + return dex_scheduler_spawn (NULL, 0, + sysprof_sampler_record_fiber, + record, + (GDestroyNotify)record_free); +} + +static void +sysprof_sampler_finalize (GObject *object) +{ + SysprofSampler *self = (SysprofSampler *)object; + + g_clear_pointer (&self->perf_event_streams, g_ptr_array_unref); + + G_OBJECT_CLASS (sysprof_sampler_parent_class)->finalize (object); +} + +static void +sysprof_sampler_class_init (SysprofSamplerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + object_class->finalize = sysprof_sampler_finalize; + + instrument_class->list_required_policy = sysprof_sampler_list_required_policy; + instrument_class->prepare = sysprof_sampler_prepare; + instrument_class->record = sysprof_sampler_record; +} + +static void +sysprof_sampler_init (SysprofSampler *self) +{ + self->perf_event_streams = g_ptr_array_new_with_free_func (g_object_unref); +} + +SysprofInstrument * +sysprof_sampler_new (void) +{ + return g_object_new (SYSPROF_TYPE_SAMPLER, NULL); +} diff --git a/src/libsysprof/sysprof-perf-source.h b/src/libsysprof/sysprof-sampler.h similarity index 50% rename from src/libsysprof/sysprof-perf-source.h rename to src/libsysprof/sysprof-sampler.h index c6fec8f5..4a7309b7 100644 --- a/src/libsysprof/sysprof-perf-source.h +++ b/src/libsysprof/sysprof-sampler.h @@ -1,6 +1,6 @@ -/* sysprof-perf-source.h +/* sysprof-sampler.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,24 +20,23 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-source.h" -#include "sysprof-version-macros.h" +#include "sysprof-instrument.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_PERF_SOURCE (sysprof_perf_source_get_type()) +#define SYSPROF_TYPE_SAMPLER (sysprof_sampler_get_type()) +#define SYSPROF_IS_SAMPLER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_SAMPLER) +#define SYSPROF_SAMPLER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_SAMPLER, SysprofSampler) +#define SYSPROF_SAMPLER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_SAMPLER, SysprofSamplerClass) + +typedef struct _SysprofSampler SysprofSampler; +typedef struct _SysprofSamplerClass SysprofSamplerClass; SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofPerfSource, sysprof_perf_source, SYSPROF, PERF_SOURCE, GObject) +GType sysprof_sampler_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_sampler_new (void); -SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_perf_source_new (void); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_perf_source_set_target_pid (SysprofPerfSource *self, - GPid pid); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofSampler, g_object_unref) G_END_DECLS diff --git a/src/libsysprof/sysprof-selection.c b/src/libsysprof/sysprof-selection.c deleted file mode 100644 index 521aff53..00000000 --- a/src/libsysprof/sysprof-selection.c +++ /dev/null @@ -1,335 +0,0 @@ -/* sysprof-selection.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-selection" - -#include "config.h" - -#include "sysprof-selection.h" - -struct _SysprofSelection -{ - GObject parent_instance; - GArray *ranges; -}; - -typedef struct -{ - gint64 begin; - gint64 end; -} Range; - -G_DEFINE_TYPE (SysprofSelection, sysprof_selection, G_TYPE_OBJECT) - -enum { - PROP_0, - PROP_HAS_SELECTION, - N_PROPS -}; - -enum { - CHANGED, - N_SIGNALS -}; - -static GParamSpec *properties [N_PROPS]; -static guint signals [N_SIGNALS]; - -static inline void -int64_swap (gint64 *a, - gint64 *b) -{ - if (*a > *b) - { - gint64 tmp = *a; - *a = *b; - *b = tmp; - } -} - -static void -sysprof_selection_finalize (GObject *object) -{ - SysprofSelection *self = (SysprofSelection *)object; - - g_clear_pointer (&self->ranges, g_array_unref); - - G_OBJECT_CLASS (sysprof_selection_parent_class)->finalize (object); -} - -static void -sysprof_selection_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofSelection *self = SYSPROF_SELECTION (object); - - switch (prop_id) - { - case PROP_HAS_SELECTION: - g_value_set_boolean (value, sysprof_selection_get_has_selection (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_selection_class_init (SysprofSelectionClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_selection_finalize; - object_class->get_property = sysprof_selection_get_property; - - properties [PROP_HAS_SELECTION] = - g_param_spec_boolean ("has-selection", - "Has Selection", - "Has Selection", - FALSE, - (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); - - /** - * SysprofSelection::changed: - * - * This signal is emitted when the selection has changed. - */ - signals [CHANGED] = - g_signal_new ("changed", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, G_TYPE_NONE, 0); -} - -static void -sysprof_selection_init (SysprofSelection *self) -{ - self->ranges = g_array_new (FALSE, FALSE, sizeof (Range)); -} - -gboolean -sysprof_selection_get_has_selection (SysprofSelection *self) -{ - g_return_val_if_fail (SYSPROF_IS_SELECTION (self), FALSE); - - return self->ranges->len > 0; -} - -/** - * sysprof_selection_foreach: - * @self: A #SysprofSelection - * @foreach_func: (scope call): a callback for each range - * @user_data: user data for @foreach_func - * - * Calls @foreach_func for every selected range. - */ -void -sysprof_selection_foreach (SysprofSelection *self, - SysprofSelectionForeachFunc foreach_func, - gpointer user_data) -{ - g_return_if_fail (SYSPROF_IS_SELECTION (self)); - g_return_if_fail (foreach_func != NULL); - - for (guint i = 0; i < self->ranges->len; i++) - { - const Range *range = &g_array_index (self->ranges, Range, i); - foreach_func (self, range->begin, range->end, user_data); - } -} - -static gint -range_compare (gconstpointer a, - gconstpointer b) -{ - const Range *ra = a; - const Range *rb = b; - - if (ra->begin < rb->begin) - return -1; - - if (rb->begin < ra->begin) - return 1; - - if (ra->end < rb->end) - return -1; - - if (rb->end < ra->end) - return 1; - - return 0; -} - -static void -join_overlapping (GArray *ranges) -{ - if (ranges->len > 1) - { - guint i = 0; - - while (i < ranges->len - 1) - { - Range *range; - Range next; - - range = &g_array_index (ranges, Range, i); - next = g_array_index (ranges, Range, i + 1); - - if (range->end > next.begin) - { - range->end = next.end; - g_array_remove_index (ranges, i + 1); - } - else - { - i++; - } - } - } -} - -void -sysprof_selection_select_range (SysprofSelection *self, - gint64 begin_time, - gint64 end_time) -{ - Range range = { 0 }; - - g_return_if_fail (SYSPROF_IS_SELECTION (self)); - - int64_swap (&begin_time, &end_time); - - range.begin = begin_time; - range.end = end_time; - - g_array_append_val (self->ranges, range); - g_array_sort (self->ranges, range_compare); - join_overlapping (self->ranges); - - if (self->ranges->len == 1) - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SELECTION]); - - g_signal_emit (self, signals [CHANGED], 0); -} - -void -sysprof_selection_unselect_range (SysprofSelection *self, - gint64 begin, - gint64 end) -{ - g_return_if_fail (SYSPROF_IS_SELECTION (self)); - - int64_swap (&begin, &end); - - for (guint i = 0; i < self->ranges->len; i++) - { - const Range *range = &g_array_index (self->ranges, Range, i); - - if (range->begin == begin && range->end == end) - { - g_array_remove_index (self->ranges, i); - if (self->ranges->len == 0) - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SELECTION]); - g_signal_emit (self, signals [CHANGED], 0); - break; - } - } -} - -void -sysprof_selection_unselect_all (SysprofSelection *self) -{ - g_return_if_fail (SYSPROF_IS_SELECTION (self)); - - if (self->ranges->len > 0) - { - g_array_remove_range (self->ranges, 0, self->ranges->len); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SELECTION]); - g_signal_emit (self, signals [CHANGED], 0); - } -} - -gboolean -sysprof_selection_contains (SysprofSelection *self, - gint64 time_at) -{ - if (self == NULL || self->ranges->len == 0) - return TRUE; - - for (guint i = 0; i < self->ranges->len; i++) - { - const Range *range = &g_array_index (self->ranges, Range, i); - - if (time_at >= range->begin && time_at <= range->end) - return TRUE; - } - - return FALSE; -} - -SysprofSelection * -sysprof_selection_copy (const SysprofSelection *self) -{ - SysprofSelection *copy; - - if (self == NULL) - return NULL; - - copy = g_object_new (SYSPROF_TYPE_SELECTION, NULL); - - for (guint i = 0; i < self->ranges->len; i++) - { - Range range = g_array_index (self->ranges, Range, i); - g_array_append_val (copy->ranges, range); - } - - return copy; -} - -guint -sysprof_selection_get_n_ranges (SysprofSelection *self) -{ - g_return_val_if_fail (SYSPROF_IS_SELECTION (self), 0); - return self->ranges ? self->ranges->len : 0; -} - -void -sysprof_selection_get_nth_range (SysprofSelection *self, - guint nth, - gint64 *begin_time, - gint64 *end_time) -{ - Range r = {0}; - - g_return_if_fail (SYSPROF_IS_SELECTION (self)); - - if (self->ranges && nth < self->ranges->len) - r = g_array_index (self->ranges, Range, nth); - - if (begin_time) - *begin_time = r.begin; - - if (end_time) - *end_time = r.end; -} diff --git a/src/libsysprof/sysprof-selection.h b/src/libsysprof/sysprof-selection.h deleted file mode 100644 index 53141a83..00000000 --- a/src/libsysprof/sysprof-selection.h +++ /dev/null @@ -1,72 +0,0 @@ -/* sysprof-selection.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_SELECTION (sysprof_selection_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofSelection, sysprof_selection, SYSPROF, SELECTION, GObject) - -typedef void (*SysprofSelectionForeachFunc) (SysprofSelection *self, - gint64 begin_time, - gint64 end_time, - gpointer user_data); - -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_selection_get_has_selection (SysprofSelection *self); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_selection_contains (SysprofSelection *self, - gint64 time_at); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_selection_select_range (SysprofSelection *self, - gint64 begin_time, - gint64 end_time); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_selection_unselect_range (SysprofSelection *self, - gint64 begin, - gint64 end); -SYSPROF_AVAILABLE_IN_ALL -guint sysprof_selection_get_n_ranges (SysprofSelection *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_selection_get_nth_range (SysprofSelection *self, - guint nth, - gint64 *begin_time, - gint64 *end_time); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_selection_unselect_all (SysprofSelection *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_selection_foreach (SysprofSelection *self, - SysprofSelectionForeachFunc foreach_func, - gpointer user_data); -SYSPROF_AVAILABLE_IN_ALL -SysprofSelection *sysprof_selection_copy (const SysprofSelection *self); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-source.c b/src/libsysprof/sysprof-source.c deleted file mode 100644 index 22a9e13d..00000000 --- a/src/libsysprof/sysprof-source.c +++ /dev/null @@ -1,189 +0,0 @@ -/* sysprof-source.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include "sysprof-source.h" - -G_DEFINE_INTERFACE (SysprofSource, sysprof_source, G_TYPE_OBJECT) - -enum { - FAILED, - FINISHED, - READY, - N_SIGNALS -}; - -static guint signals [N_SIGNALS]; - -static void -sysprof_source_default_init (SysprofSourceInterface *iface) -{ - signals [FAILED] = g_signal_new ("failed", - G_TYPE_FROM_INTERFACE (iface), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, NULL, - G_TYPE_NONE, 1, G_TYPE_ERROR); - - signals [FINISHED] = g_signal_new ("finished", - G_TYPE_FROM_INTERFACE (iface), - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, G_TYPE_NONE, 0); - - signals [READY] = g_signal_new ("ready", - G_TYPE_FROM_INTERFACE (iface), - G_SIGNAL_RUN_LAST, - 0, NULL, NULL, NULL, G_TYPE_NONE, 0); -} - -void -sysprof_source_add_pid (SysprofSource *self, - GPid pid) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (pid != FALSE); - - if (SYSPROF_SOURCE_GET_IFACE (self)->add_pid) - SYSPROF_SOURCE_GET_IFACE (self)->add_pid (self, pid); -} - -void -sysprof_source_emit_finished (SysprofSource *self) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - - g_signal_emit (self, signals [FINISHED], 0); -} - -void -sysprof_source_emit_failed (SysprofSource *self, - const GError *error) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (error != NULL); - - g_signal_emit (self, signals [FAILED], 0, error); -} - -void -sysprof_source_emit_ready (SysprofSource *self) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - - g_signal_emit (self, signals [READY], 0); -} - -gboolean -sysprof_source_get_is_ready (SysprofSource *self) -{ - g_return_val_if_fail (SYSPROF_IS_SOURCE (self), FALSE); - - if (SYSPROF_SOURCE_GET_IFACE (self)->get_is_ready) - return SYSPROF_SOURCE_GET_IFACE (self)->get_is_ready (self); - - return TRUE; -} - -void -sysprof_source_prepare (SysprofSource *self) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - - if (SYSPROF_SOURCE_GET_IFACE (self)->prepare) - SYSPROF_SOURCE_GET_IFACE (self)->prepare (self); -} - -void -sysprof_source_set_writer (SysprofSource *self, - SysprofCaptureWriter *writer) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (writer != NULL); - - if (SYSPROF_SOURCE_GET_IFACE (self)->set_writer) - SYSPROF_SOURCE_GET_IFACE (self)->set_writer (self, writer); -} - -void -sysprof_source_start (SysprofSource *self) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - - if (SYSPROF_SOURCE_GET_IFACE (self)->start) - SYSPROF_SOURCE_GET_IFACE (self)->start (self); -} - -void -sysprof_source_stop (SysprofSource *self) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - - if (SYSPROF_SOURCE_GET_IFACE (self)->stop) - SYSPROF_SOURCE_GET_IFACE (self)->stop (self); -} - -void -sysprof_source_modify_spawn (SysprofSource *self, - SysprofSpawnable *spawnable) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (SYSPROF_IS_SPAWNABLE (spawnable)); - - if (SYSPROF_SOURCE_GET_IFACE (self)->modify_spawn) - SYSPROF_SOURCE_GET_IFACE (self)->modify_spawn (self, spawnable); -} - -void -sysprof_source_serialize (SysprofSource *self, - GKeyFile *keyfile, - const gchar *group) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (keyfile != NULL); - g_return_if_fail (group != NULL); - - if (SYSPROF_SOURCE_GET_IFACE (self)->serialize) - SYSPROF_SOURCE_GET_IFACE (self)->serialize (self, keyfile, group); -} - -void -sysprof_source_deserialize (SysprofSource *self, - GKeyFile *keyfile, - const gchar *group) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (keyfile != NULL); - g_return_if_fail (group != NULL); - - if (SYSPROF_SOURCE_GET_IFACE (self)->deserialize) - SYSPROF_SOURCE_GET_IFACE (self)->deserialize (self, keyfile, group); -} - -void -sysprof_source_supplement (SysprofSource *self, - SysprofCaptureReader *reader) -{ - g_return_if_fail (SYSPROF_IS_SOURCE (self)); - g_return_if_fail (reader != NULL); - - if (SYSPROF_SOURCE_GET_IFACE (self)->supplement) - SYSPROF_SOURCE_GET_IFACE (self)->supplement (self, reader); -} diff --git a/src/libsysprof/sysprof-source.h b/src/libsysprof/sysprof-source.h deleted file mode 100644 index 412aa765..00000000 --- a/src/libsysprof/sysprof-source.h +++ /dev/null @@ -1,212 +0,0 @@ -/* sysprof-source.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include -#include -#include - -#include "sysprof-spawnable.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_SOURCE (sysprof_source_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_INTERFACE (SysprofSource, sysprof_source, SYSPROF, SOURCE, GObject) - -struct _SysprofSourceInterface -{ - GTypeInterface parent_iface; - - /** - * SysprofSource::get_is_ready: - * @self: A SysprofSource. - * - * This function should return %TRUE if the source is ready to start - * profiling. If the source is not ready until after sysprof_source_start() has - * been called, use sysprof_source_emit_ready() to notify the profiler that the - * source is ready for profiling. - * - * Returns: %TRUE if the source is ready to start profiling. - */ - gboolean (*get_is_ready) (SysprofSource *self); - - /** - * SysprofSource::set_writer: - * @self: A #SysprofSource. - * @writer: A #SysprofCaptureWriter - * - * Sets the #SysprofCaptureWriter to use when profiling. @writer is only safe to - * use from the main thread. If you need to capture from a thread, you should - * create a memory-based #SysprofCaptureWriter and then splice that into this - * writer from the main thread when profiling completes. - * - * See sysprof_capture_writer_splice() for information on splicing writers. - */ - void (*set_writer) (SysprofSource *self, - SysprofCaptureWriter *writer); - - /** - * SysprofSource::prepare: - * - * This function is called before profiling has started. The source should - * prepare any pre-profiling setup here. It may perform this work - * asynchronously, but must g_object_notify() the SysprofSource::is-ready - * property once that asynchronous work has been performed. Until it - * is ready, #SysprofSource::is-ready must return FALSE. - */ - void (*prepare) (SysprofSource *self); - - /** - * SysprofSource::add_pid: - * @self: A #SysprofSource - * @pid: A pid_t > -1 - * - * This function is used to notify the #SysprofSource that a new process, - * identified by @pid, should be profiled. By default, sources should - * assume all processes, and only restrict to a given set of pids if - * this function is called. - */ - void (*add_pid) (SysprofSource *self, - GPid pid); - - /** - * SysprofSource::start: - * @self: A #SysprofSource. - * - * Start profiling as configured. - * - * If a failure occurs while processing, the source should notify the - * profiling session via sysprof_source_emit_failed() from the main thread. - */ - void (*start) (SysprofSource *self); - - /** - * SysprofSource::stop: - * @self: A #SysprofSource. - * - * Stop capturing a profile. The source should immediately stop - * profiling and perform any cleanup tasks required. If doing - * off-main-thread capturing, this is a good time to splice your - * capture into the capture file set with sysprof_source_set_writer(). - * - * If you need to perform asynchronous cleanup, call - * sysprof_source_emit_finished() once that work has completed. If you do - * not need to perform asynchronous cleanup, call - * sysprof_source_emit_finished() from this function. - * - * sysprof_source_emit_finished() must be called from the main-thread. - */ - void (*stop) (SysprofSource *self); - - /** - * SysprofSource::modify-spawn: - * @self: a #SysprofSource - * @spawnable: a #SysprofSpawnable - * - * Allows the source to modify the launcher or argv before the - * process is spawned. - */ - void (*modify_spawn) (SysprofSource *self, - SysprofSpawnable *spawnable); - - /** - * SysprofSource::supplement: - * - * The "supplement" vfunc is called when a source should attempt to add - * any additional data to the trace file based on existing data within - * the trace file. A #SysprofCaptureReader is provided to simplify this - * process for the vfunc. It should write to the writer provided in - * sysprof_source_set_writer(). - * - * This function must finish synchronously. - */ - void (*supplement) (SysprofSource *self, - SysprofCaptureReader *reader); - - /** - * SysprofSource::serialize: - * @self: a #SysprofSource - * @keyfile: a #GKeyFile - * @group: the keyfile group to use - * - * Requests that the source serialize itself into the keyfile - * so that it may be replayed at a future point in time. - */ - void (*serialize) (SysprofSource *self, - GKeyFile *keyfile, - const gchar *group); - - /** - * SysprofSource::deserialize: - * @self: a #SysprofSource - * @keyfile: a #GKeyFile - * @group: the keyfile group to use - * - * Deserialize from the saved state. - */ - void (*deserialize) (SysprofSource *self, - GKeyFile *keyfile, - const gchar *group); -}; - -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_add_pid (SysprofSource *self, - GPid pid); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_emit_ready (SysprofSource *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_emit_finished (SysprofSource *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_emit_failed (SysprofSource *self, - const GError *error); -SYSPROF_AVAILABLE_IN_ALL -gboolean sysprof_source_get_is_ready (SysprofSource *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_prepare (SysprofSource *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_set_writer (SysprofSource *self, - SysprofCaptureWriter *writer); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_start (SysprofSource *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_stop (SysprofSource *self); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_modify_spawn (SysprofSource *self, - SysprofSpawnable *spawnable); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_serialize (SysprofSource *self, - GKeyFile *keyfile, - const gchar *group); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_deserialize (SysprofSource *self, - GKeyFile *keyfile, - const gchar *group); -SYSPROF_AVAILABLE_IN_ALL -void sysprof_source_supplement (SysprofSource *self, - SysprofCaptureReader *reader); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-spawnable.c b/src/libsysprof/sysprof-spawnable.c index b152c1b2..6b4fe15c 100644 --- a/src/libsysprof/sysprof-spawnable.c +++ b/src/libsysprof/sysprof-spawnable.c @@ -18,12 +18,12 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#define G_LOG_DOMAIN "sysprof-spawnable" - #include "config.h" #include +#include + #include "sysprof-spawnable.h" typedef struct @@ -65,8 +65,6 @@ sysprof_spawnable_new (void) * Gets the subprocess flags for spawning. * * Returns: the #GSubprocessFlags bitwise-or'd - * - * Since: 3.46 */ GSubprocessFlags sysprof_spawnable_get_flags (SysprofSpawnable *self) @@ -81,8 +79,6 @@ sysprof_spawnable_get_flags (SysprofSpawnable *self) * @self: a #SysprofSpawnable * * Set the flags to use when spawning the process. - * - * Since: 3.46 */ void sysprof_spawnable_set_flags (SysprofSpawnable *self, @@ -127,6 +123,8 @@ sysprof_spawnable_init (SysprofSpawnable *self) { self->next_fd = 3; + self->environ = g_get_environ (); + self->argv = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (self->argv, NULL); @@ -270,8 +268,6 @@ sysprof_spawnable_foreach_fd (SysprofSpawnable *self, * will be taken and therefore unknown to the spawnable. * * The default for this is 2. - * - * Since: 3.34 */ void sysprof_spawnable_set_starting_fd (SysprofSpawnable *self, @@ -292,8 +288,6 @@ sysprof_spawnable_set_starting_fd (SysprofSpawnable *self, * * Returns: (transfer full): a #GSubprocess or %NULL on failure and * @error is set. - * - * Since: 3.34 */ GSubprocess * sysprof_spawnable_spawn (SysprofSpawnable *self, @@ -326,15 +320,83 @@ sysprof_spawnable_spawn (SysprofSpawnable *self, return g_subprocess_launcher_spawnv (launcher, argv, error); } +const char * +sysprof_spawnable_get_cwd (SysprofSpawnable *self) +{ + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), NULL); + + return self->cwd; +} + void sysprof_spawnable_set_cwd (SysprofSpawnable *self, const gchar *cwd) { g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); - if (g_strcmp0 (cwd, self->cwd) != 0) - { - g_free (self->cwd); - self->cwd = g_strdup (cwd); - } + g_set_str (&self->cwd, cwd); +} + +/** + * sysprof_spawnable_add_trace_fd: + * @self: a #SysprofSpawnable + * @envvar: (nullable): the environment variable + * + * Adds an environment variable to the spawnable that will contain a + * "tracing file-descriptor". The spawned process can use + * `sysprof_capture_writer_new_from_env()` if @envvar is %NULL + * or with `getenv()` and `sysprof_capture_writer_new_from_fd()`. + * + * If @envvar is %NULL, "SYSPROF_TRACE_FD" will be used. + * + * The caller is responsible for closin the resulting FD. + * + * Returns: A file-descriptor which can be used to read the trace or + * -1 upon failure and `errno` is set. Caller must `close()` the + * FD if >= 0. + */ +int +sysprof_spawnable_add_trace_fd (SysprofSpawnable *self, + const char *envvar) +{ + g_autofd int fd = -1; + g_autofd int dest = -1; + g_autofree char *name = NULL; + g_autofree char *fdstr = NULL; + + g_return_val_if_fail (SYSPROF_IS_SPAWNABLE (self), -1); + + if (envvar == NULL) + envvar = "SYSPROF_TRACE_FD"; + + name = g_strdup_printf ("[sysprof-tracefd:%s]", envvar); + + if (-1 == (fd = sysprof_memfd_create (name))) + return -1; + + if (-1 == (dest = dup (fd))) + return -1; + + fdstr = g_strdup_printf ("%d", dest); + + sysprof_spawnable_setenv (self, envvar, fdstr); + sysprof_spawnable_take_fd (self, g_steal_fd (&dest), -1); + + return g_steal_fd (&fd); +} + +void +sysprof_spawnable_add_ld_preload (SysprofSpawnable *self, + const char *library_path) +{ + g_autofree char *amended = NULL; + const char *val; + + g_return_if_fail (SYSPROF_IS_SPAWNABLE (self)); + g_return_if_fail (library_path != NULL); + + if ((val = sysprof_spawnable_getenv (self, "LD_PRELOAD"))) + library_path = amended = g_strdup_printf ("%s:%s", val, library_path); + + sysprof_spawnable_setenv (self, "LD_PRELOAD", library_path); } diff --git a/src/libsysprof/sysprof-spawnable.h b/src/libsysprof/sysprof-spawnable.h index e6d835bf..f427f8de 100644 --- a/src/libsysprof/sysprof-spawnable.h +++ b/src/libsysprof/sysprof-spawnable.h @@ -22,7 +22,7 @@ #include -#include "sysprof-version-macros.h" +#include G_BEGIN_DECLS @@ -39,30 +39,32 @@ SYSPROF_AVAILABLE_IN_ALL SysprofSpawnable *sysprof_spawnable_new (void); SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_prepend_argv (SysprofSpawnable *self, - const gchar *argv); + const char *argv); SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_append_argv (SysprofSpawnable *self, - const gchar *argv); + const char *argv); SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_append_args (SysprofSpawnable *self, - const gchar * const *argv); + const char * const *argv); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_spawnable_get_cwd (SysprofSpawnable *self); SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_set_cwd (SysprofSpawnable *self, - const gchar *cwd); + const char *cwd); SYSPROF_AVAILABLE_IN_ALL -const gchar * const *sysprof_spawnable_get_argv (SysprofSpawnable *self); +const char * const *sysprof_spawnable_get_argv (SysprofSpawnable *self); SYSPROF_AVAILABLE_IN_ALL -const gchar * const *sysprof_spawnable_get_environ (SysprofSpawnable *self); +const char * const *sysprof_spawnable_get_environ (SysprofSpawnable *self); SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_set_environ (SysprofSpawnable *self, - const gchar * const *environ); + const char * const *environ); SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_setenv (SysprofSpawnable *self, - const gchar *key, - const gchar *value); + const char *key, + const char *value); SYSPROF_AVAILABLE_IN_ALL -const gchar *sysprof_spawnable_getenv (SysprofSpawnable *self, - const gchar *key); +const char *sysprof_spawnable_getenv (SysprofSpawnable *self, + const char *key); SYSPROF_AVAILABLE_IN_ALL gint sysprof_spawnable_take_fd (SysprofSpawnable *self, gint fd, @@ -75,11 +77,17 @@ SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_set_starting_fd (SysprofSpawnable *self, gint starting_fd); SYSPROF_AVAILABLE_IN_ALL +int sysprof_spawnable_add_trace_fd (SysprofSpawnable *self, + const char *envvar); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_spawnable_add_ld_preload (SysprofSpawnable *self, + const char *library_path); +SYSPROF_AVAILABLE_IN_ALL GSubprocess *sysprof_spawnable_spawn (SysprofSpawnable *self, GError **error); -SYSPROF_AVAILABLE_IN_3_46 +SYSPROF_AVAILABLE_IN_ALL GSubprocessFlags sysprof_spawnable_get_flags (SysprofSpawnable *self); -SYSPROF_AVAILABLE_IN_3_46 +SYSPROF_AVAILABLE_IN_ALL void sysprof_spawnable_set_flags (SysprofSpawnable *self, GSubprocessFlags flags); diff --git a/src/libsysprof/sysprof-strings-private.h b/src/libsysprof/sysprof-strings-private.h new file mode 100644 index 00000000..8ea14608 --- /dev/null +++ b/src/libsysprof/sysprof-strings-private.h @@ -0,0 +1,57 @@ +/* sysprof-strings-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +typedef struct _SysprofStrings SysprofStrings; + +SysprofStrings *sysprof_strings_new (void); +SysprofStrings *sysprof_strings_ref (SysprofStrings *self); +void sysprof_strings_unref (SysprofStrings *self); +GRefString *sysprof_strings_get (SysprofStrings *self, + const char *string); + +#define SYSPROF_STRV_INIT(...) ((const char * const[]){__VA_ARGS__,NULL}) + +static inline gboolean +sysprof_set_strv (char ***dest, + const char * const *src) +{ + if ((const char * const *)*dest == src) + return FALSE; + + if (*dest == NULL || + src == NULL || + !g_strv_equal ((const char * const *)*dest, src)) + { + char **copy = g_strdupv ((char **)src); + g_strfreev (*dest); + *dest = copy; + return TRUE; + } + + return FALSE; +} + +G_END_DECLS diff --git a/src/libsysprof/sysprof-strings.c b/src/libsysprof/sysprof-strings.c new file mode 100644 index 00000000..421da461 --- /dev/null +++ b/src/libsysprof/sysprof-strings.c @@ -0,0 +1,86 @@ +/* sysprof-strings.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-strings-private.h" + +struct _SysprofStrings +{ + GMutex mutex; + GHashTable *hashtable; +}; + +SysprofStrings * +sysprof_strings_new (void) +{ + SysprofStrings *self; + + self = g_atomic_rc_box_new0 (SysprofStrings); + g_mutex_init (&self->mutex); + self->hashtable = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_ref_string_release, + NULL); + + return self; +} + +SysprofStrings * +sysprof_strings_ref (SysprofStrings *self) +{ + return g_atomic_rc_box_acquire (self); +} + +static void +sysprof_strings_finalize (gpointer data) +{ + SysprofStrings *self = data; + + g_mutex_clear (&self->mutex); + g_clear_pointer (&self->hashtable, g_hash_table_unref); +} + +void +sysprof_strings_unref (SysprofStrings *self) +{ + g_atomic_rc_box_release_full (self, sysprof_strings_finalize); +} + +GRefString * +sysprof_strings_get (SysprofStrings *self, + const char *string) +{ + GRefString *ret; + + if (string == NULL) + return NULL; + + g_mutex_lock (&self->mutex); + if (!(ret = g_hash_table_lookup (self->hashtable, string))) + { + ret = g_ref_string_new (string); + g_hash_table_insert (self->hashtable, ret, ret); + } + g_ref_string_acquire (ret); + g_mutex_unlock (&self->mutex); + + return ret; +} diff --git a/src/libsysprof/sysprof-symbol-cache-private.h b/src/libsysprof/sysprof-symbol-cache-private.h new file mode 100644 index 00000000..4c96130f --- /dev/null +++ b/src/libsysprof/sysprof-symbol-cache-private.h @@ -0,0 +1,44 @@ +/* sysprof-symbol-cache-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-symbol.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SYMBOL_CACHE (sysprof_symbol_cache_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofSymbolCache, sysprof_symbol_cache, SYSPROF, SYMBOL_CACHE, GObject) + +SysprofSymbolCache *sysprof_symbol_cache_new (void); +SysprofSymbol *sysprof_symbol_cache_lookup (SysprofSymbolCache *self, + SysprofAddress address); +void sysprof_symbol_cache_take (SysprofSymbolCache *self, + SysprofSymbol *symbol); +void sysprof_symbol_cache_populate_packed (SysprofSymbolCache *self, + GArray *array, + GByteArray *strings, + GHashTable *strings_offset, + int pid); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbol-cache.c b/src/libsysprof/sysprof-symbol-cache.c new file mode 100644 index 00000000..0166ed44 --- /dev/null +++ b/src/libsysprof/sysprof-symbol-cache.c @@ -0,0 +1,265 @@ +/* sysprof-symbol-cache.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +typedef struct _SysprofSymbolCacheNode SysprofSymbolCacheNode; +static void sysprof_symbol_cache_node_augment (SysprofSymbolCacheNode *node); +#define RB_AUGMENT(elem) sysprof_symbol_cache_node_augment(elem) + +#include "tree.h" + +#include "sysprof-bundled-symbolizer-private.h" +#include "sysprof-symbol-private.h" +#include "sysprof-symbol-cache-private.h" + +struct _SysprofSymbolCacheNode +{ + RB_ENTRY(_SysprofSymbolCacheNode) link; + SysprofSymbol *symbol; + guint64 low; + guint64 high; + guint64 max; +}; + +struct _SysprofSymbolCache +{ + GObject parent_instance; + RB_HEAD(sysprof_symbol_cache, _SysprofSymbolCacheNode) head; +}; + +G_DEFINE_FINAL_TYPE (SysprofSymbolCache, sysprof_symbol_cache, G_TYPE_OBJECT) + +static inline int +sysprof_symbol_cache_node_compare (SysprofSymbolCacheNode *a, + SysprofSymbolCacheNode *b) +{ + if (a->low < b->low) + return -1; + else if (a->low > b->low) + return 1; + else + return 0; +} + +RB_GENERATE_STATIC(sysprof_symbol_cache, _SysprofSymbolCacheNode, link, sysprof_symbol_cache_node_compare); + +static void +sysprof_symbol_cache_node_augment (SysprofSymbolCacheNode *node) +{ + node->max = node->high; + + if (RB_LEFT(node, link) && RB_LEFT(node, link)->max > node->max) + node->max = RB_LEFT(node, link)->max; + + if (RB_RIGHT(node, link) && RB_RIGHT(node, link)->max > node->max) + node->max = RB_RIGHT(node, link)->max; +} + +static void +sysprof_symbol_cache_node_finalize (SysprofSymbolCacheNode *node) +{ + g_clear_object (&node->symbol); + g_free (node); +} + +static void +sysprof_symbol_cache_node_free (SysprofSymbolCacheNode *node) +{ + SysprofSymbolCacheNode *right = RB_RIGHT(node, link); + SysprofSymbolCacheNode *left = RB_LEFT(node, link); + + if (left != NULL) + sysprof_symbol_cache_node_free (left); + + sysprof_symbol_cache_node_finalize (node); + + if (right != NULL) + sysprof_symbol_cache_node_free (right); +} + +static void +sysprof_symbol_cache_finalize (GObject *object) +{ + SysprofSymbolCache *self = (SysprofSymbolCache *)object; + SysprofSymbolCacheNode *node = RB_ROOT(&self->head); + + if (node != NULL) + sysprof_symbol_cache_node_free (node); + + G_OBJECT_CLASS (sysprof_symbol_cache_parent_class)->finalize (object); +} + +static void +sysprof_symbol_cache_class_init (SysprofSymbolCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_symbol_cache_finalize; +} + +static void +sysprof_symbol_cache_init (SysprofSymbolCache *self) +{ + RB_INIT (&self->head); +} + +SysprofSymbolCache * +sysprof_symbol_cache_new (void) +{ + return g_object_new (SYSPROF_TYPE_SYMBOL_CACHE, NULL); +} + +void +sysprof_symbol_cache_take (SysprofSymbolCache *self, + SysprofSymbol *symbol) +{ + SysprofSymbolCacheNode *node; + SysprofSymbolCacheNode *parent; + SysprofSymbolCacheNode *ret; + + g_return_if_fail (SYSPROF_IS_SYMBOL_CACHE (self)); + g_return_if_fail (SYSPROF_IS_SYMBOL (symbol)); + g_return_if_fail (symbol->end_address > symbol->begin_address); + + /* Some symbols are not suitable for our interval tree */ + if (symbol->begin_address == 0 || + symbol->end_address == 0 || + symbol->begin_address == symbol->end_address) + { + g_object_unref (symbol); + return; + } + + node = g_new0 (SysprofSymbolCacheNode, 1); + node->symbol = symbol; + node->low = symbol->begin_address; + node->high = symbol->end_address-1; + node->max = node->high; + + /* If there is a collision, then the node is returned. Otherwise + * if the node was inserted, NULL is returned. + */ + if ((ret = RB_INSERT(sysprof_symbol_cache, &self->head, node))) + { + sysprof_symbol_cache_node_free (node); + return; + } + + parent = RB_PARENT(node, link); + + while (parent != NULL) + { + if (node->max > parent->max) + parent->max = node->max; + node = parent; + parent = RB_PARENT(parent, link); + } +} + +SysprofSymbol * +sysprof_symbol_cache_lookup (SysprofSymbolCache *self, + SysprofAddress address) +{ + SysprofSymbolCacheNode *node; + + g_return_val_if_fail (SYSPROF_IS_SYMBOL_CACHE (self), NULL); + + if (address == 0) + return NULL; + + node = RB_ROOT(&self->head); + + /* The root node contains our calculated max as augmented in RBTree. + * Therefore, we can know if @address falls beyond the upper bound + * in O(1) without having to add a branch to the while loop below. + */ + if (node == NULL || node->max < address) + return NULL; + + while (node != NULL) + { + g_assert (RB_LEFT(node, link) == NULL || + node->max >= RB_LEFT(node, link)->max); + g_assert (RB_RIGHT(node, link) == NULL || + node->max >= RB_RIGHT(node, link)->max); + + if (address >= node->low && address <= node->high) + return node->symbol; + + if (RB_LEFT(node, link) && RB_LEFT(node, link)->max >= address) + node = RB_LEFT(node, link); + else + node = RB_RIGHT(node, link); + } + + return NULL; +} + +static guint +get_string (GByteArray *strings, + GHashTable *strings_offset, + const char *string) +{ + guint pos; + + if (string == NULL || string[0] == 0) + return 0; + + pos = GPOINTER_TO_UINT (g_hash_table_lookup (strings_offset, string)); + + if (pos == 0) + { + pos = strings->len; + g_byte_array_append (strings, (const guint8 *)string, strlen (string) + 1); + g_hash_table_insert (strings_offset, (char *)string, GUINT_TO_POINTER (pos)); + } + + return pos; +} + +void +sysprof_symbol_cache_populate_packed (SysprofSymbolCache *self, + GArray *array, + GByteArray *strings, + GHashTable *strings_offset, + int pid) +{ + SysprofSymbolCacheNode *node; + + g_return_if_fail (SYSPROF_IS_SYMBOL_CACHE (self)); + g_return_if_fail (array != NULL); + + RB_FOREACH(node, sysprof_symbol_cache, &self->head) { + SysprofPackedSymbol packed; + SysprofSymbol *symbol = node->symbol; + + if (symbol->is_fallback) + continue; + + packed.addr_begin = symbol->begin_address; + packed.addr_end = symbol->end_address; + packed.pid = pid; + packed.offset = get_string (strings, strings_offset, symbol->name); + packed.tag_offset = get_string (strings, strings_offset, symbol->binary_nick); + + g_array_append_val (array, packed); + } +} diff --git a/src/libsysprof/sysprof-symbol-map.c b/src/libsysprof/sysprof-symbol-map.c deleted file mode 100644 index 2ff57d54..00000000 --- a/src/libsysprof/sysprof-symbol-map.c +++ /dev/null @@ -1,593 +0,0 @@ -/* sysprof-symbol-map.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-symbol-map" - -#include "config.h" - -#include -#include -#include - -#include "sysprof-map-lookaside.h" -#include "sysprof-symbol-map.h" - -/* - * Because we can't rely on the address ranges of symbols from ELF files - * or elsewhere, we have to duplicate a lot of entries when building this - * so that we can resolve all of the corrent addresses. - */ - -SYSPROF_ALIGNED_BEGIN(1) -typedef struct -{ - SysprofCaptureAddress addr_begin; - SysprofCaptureAddress addr_end; - guint32 pid; - guint32 offset; - guint32 tag_offset; - guint32 padding; -} Decoded -SYSPROF_ALIGNED_END(1); - -struct _SysprofSymbolMap -{ - /* For creating maps */ - GStringChunk *chunk; - GHashTable *lookasides; - GPtrArray *resolvers; - GPtrArray *samples; - guint resolved : 1; - - /* For reading maps */ - GMappedFile *mapped; - const Decoded *symbols; - gsize n_symbols; - const gchar *beginptr; - const gchar *endptr; -}; - -typedef struct -{ - SysprofCaptureAddress addr; - const gchar *name; - GQuark tag; - guint32 pid; -} Element; - -static void -element_free (Element *ele) -{ - g_slice_free (Element, ele); -} - -static gint -element_compare (gconstpointer a, - gconstpointer b) -{ - const Element *aa = *(const Element **)a; - const Element *bb = *(const Element **)b; - - if (aa->pid < bb->pid) - return -1; - - if (aa->pid > bb->pid) - return 1; - - if (aa->addr < bb->addr) - return -1; - - if (aa->addr > bb->addr) - return 1; - - return 0; -} - -static guint -element_hash (gconstpointer data) -{ - const Element *ele = data; - struct { - guint32 a; - guint32 b; - } addr; - - memcpy (&addr, &ele->addr, sizeof addr); - return addr.a ^ addr.b ^ ele->pid; -} - -static gboolean -element_equal (gconstpointer a, - gconstpointer b) -{ - const Element *aa = a; - const Element *bb = b; - - return aa->pid == bb->pid && aa->addr == bb->addr; -} - -SysprofSymbolMap * -sysprof_symbol_map_new (void) -{ - SysprofSymbolMap *self; - - self = g_slice_new0 (SysprofSymbolMap); - self->samples = g_ptr_array_new_with_free_func ((GDestroyNotify) element_free); - self->chunk = g_string_chunk_new (4096*16); - self->resolvers = g_ptr_array_new_with_free_func (g_object_unref); - self->lookasides = g_hash_table_new_full (NULL, NULL, NULL, - (GDestroyNotify) sysprof_map_lookaside_free); - - return g_steal_pointer (&self); -} - -void -sysprof_symbol_map_free (SysprofSymbolMap *self) -{ - g_clear_pointer (&self->lookasides, g_hash_table_unref); - g_clear_pointer (&self->resolvers, g_ptr_array_unref); - g_clear_pointer (&self->chunk, g_string_chunk_free); - g_clear_pointer (&self->samples, g_ptr_array_unref); - g_clear_pointer (&self->mapped, g_mapped_file_unref); - self->beginptr = NULL; - self->endptr = NULL; - self->symbols = NULL; - self->n_symbols = 0; - g_slice_free (SysprofSymbolMap, self); -} - -static gint -search_for_symbol_cb (gconstpointer a, - gconstpointer b) -{ - const Decoded *key = a; - const Decoded *ele = b; - - if (key->pid < ele->pid) - return -1; - - if (key->pid > ele->pid) - return 1; - - g_assert (key->pid == ele->pid); - - if (key->addr_begin < ele->addr_begin) - return -1; - - if (key->addr_begin > ele->addr_end) - return 1; - - g_assert (key->addr_begin >= ele->addr_begin); - g_assert (key->addr_end <= ele->addr_end); - - return 0; -} - -const gchar * -sysprof_symbol_map_lookup (SysprofSymbolMap *self, - gint64 time, - gint32 pid, - SysprofCaptureAddress addr, - GQuark *tag) -{ - const Decoded *ret; - const Decoded key = { - .addr_begin = addr, - .addr_end = addr, - .pid = pid, - .offset = 0, - .tag_offset = 0, - }; - - g_assert (self != NULL); - - if (tag != NULL) - *tag = 0; - - ret = bsearch (&key, - self->symbols, - self->n_symbols, - sizeof *ret, - search_for_symbol_cb); - - if (ret == NULL || ret->offset == 0) - return NULL; - - if (tag != NULL && ret->tag_offset > 0) - { - if (ret->tag_offset < (self->endptr - self->beginptr)) - *tag = g_quark_from_string (&self->beginptr[ret->tag_offset]); - } - - if (ret->offset < (self->endptr - self->beginptr)) - return &self->beginptr[ret->offset]; - - return NULL; -} - -void -sysprof_symbol_map_add_resolver (SysprofSymbolMap *self, - SysprofSymbolResolver *resolver) -{ - g_assert (self != NULL); - g_assert (SYSPROF_IS_SYMBOL_RESOLVER (resolver)); - - g_ptr_array_add (self->resolvers, g_object_ref (resolver)); -} - -static gboolean -sysprof_symbol_map_do_alloc (SysprofSymbolMap *self, - SysprofCaptureReader *reader, - GHashTable *seen) -{ - const SysprofCaptureAllocation *ev; - - g_assert (self != NULL); - g_assert (reader != NULL); - g_assert (seen != NULL); - - if (!(ev = sysprof_capture_reader_read_allocation (reader))) - return FALSE; - - for (guint i = 0; i < ev->n_addrs; i++) - { - SysprofCaptureAddress addr = ev->addrs[i]; - - for (guint j = 0; j < self->resolvers->len; j++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (self->resolvers, j); - g_autofree gchar *name = NULL; - const gchar *cname; - Element ele; - GQuark tag = 0; - - name = sysprof_symbol_resolver_resolve_with_context (resolver, - ev->frame.time, - ev->frame.pid, - SYSPROF_ADDRESS_CONTEXT_USER, - addr, - &tag); - - if (name == NULL) - continue; - - cname = g_string_chunk_insert_const (self->chunk, name); - - ele.addr = addr; - ele.pid = ev->frame.pid; - ele.name = cname; - ele.tag = tag; - - if (!g_hash_table_contains (seen, &ele)) - { - Element *cpy = g_slice_dup (Element, &ele); - g_hash_table_add (seen, cpy); - g_ptr_array_add (self->samples, cpy); - } - } - } - - return TRUE; -} - -static gboolean -sysprof_symbol_map_do_sample (SysprofSymbolMap *self, - SysprofCaptureReader *reader, - GHashTable *seen) -{ - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - const SysprofCaptureSample *sample; - - g_assert (self != NULL); - g_assert (reader != NULL); - g_assert (seen != NULL); - - if (!(sample = sysprof_capture_reader_read_sample (reader))) - return FALSE; - - for (guint i = 0; i < sample->n_addrs; i++) - { - SysprofCaptureAddress addr = sample->addrs[i]; - SysprofAddressContext context; - - if (sysprof_address_is_context_switch (addr, &context)) - { - last_context = context; - continue; - } - - /* Handle backtrace() style backtraces with no context switch */ - if (last_context == SYSPROF_ADDRESS_CONTEXT_NONE) - last_context = SYSPROF_ADDRESS_CONTEXT_USER; - - for (guint j = 0; j < self->resolvers->len; j++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (self->resolvers, j); - g_autofree gchar *name = NULL; - const gchar *cname; - Element ele; - GQuark tag = 0; - - name = sysprof_symbol_resolver_resolve_with_context (resolver, - sample->frame.time, - sample->frame.pid, - last_context, - addr, - &tag); - - if (name == NULL) - continue; - - cname = g_string_chunk_insert_const (self->chunk, name); - - ele.addr = addr; - ele.pid = sample->frame.pid; - ele.name = cname; - ele.tag = tag; - - if (!g_hash_table_contains (seen, &ele)) - { - Element *cpy = g_slice_dup (Element, &ele); - g_hash_table_add (seen, cpy); - g_ptr_array_add (self->samples, cpy); - } - } - } - - return TRUE; -} - -void -sysprof_symbol_map_resolve (SysprofSymbolMap *self, - SysprofCaptureReader *reader) -{ - g_autoptr(GHashTable) seen = NULL; - SysprofCaptureFrameType type; - - g_return_if_fail (self != NULL); - g_return_if_fail (self->resolved == FALSE); - g_return_if_fail (reader != NULL); - - self->resolved = TRUE; - - seen = g_hash_table_new (element_hash, element_equal); - - sysprof_capture_reader_reset (reader); - - for (guint i = 0; i < self->resolvers->len; i++) - { - sysprof_symbol_resolver_load (g_ptr_array_index (self->resolvers, i), reader); - sysprof_capture_reader_reset (reader); - } - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_SAMPLE) - { - if (!sysprof_symbol_map_do_sample (self, reader, seen)) - break; - continue; - } - else if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - if (!sysprof_symbol_map_do_alloc (self, reader, seen)) - break; - continue; - } - - if (!sysprof_capture_reader_skip (reader)) - break; - } - - g_ptr_array_sort (self->samples, element_compare); -} - -void -sysprof_symbol_map_printf (SysprofSymbolMap *self) -{ - g_return_if_fail (self != NULL); - g_return_if_fail (self->samples != NULL); - - for (guint i = 0; i < self->samples->len; i++) - { - Element *ele = g_ptr_array_index (self->samples, i); - - if (ele->tag) - g_print ("%-5d: %"G_GUINT64_FORMAT": %s [%s]\n", ele->pid, ele->addr, ele->name, g_quark_to_string (ele->tag)); - else - g_print ("%-5d: %"G_GUINT64_FORMAT": %s\n", ele->pid, ele->addr, ele->name); - } -} - -static guint -get_string_offset (GByteArray *ar, - GHashTable *seen, - const gchar *str) -{ - gpointer ret; - - if (str == NULL) - return 0; - - if G_UNLIKELY (!g_hash_table_lookup_extended (seen, str, NULL, &ret)) - { - ret = GUINT_TO_POINTER (ar->len); - g_byte_array_append (ar, (guint8 *)str, strlen (str) + 1); - g_hash_table_insert (seen, (gpointer)str, ret); - } - - return GPOINTER_TO_UINT (ret); -} - -gboolean -sysprof_symbol_map_serialize (SysprofSymbolMap *self, - gint fd) -{ - static const Decoded empty = {0}; - SysprofCaptureAddress begin = 0; - g_autoptr(GByteArray) ar = NULL; - g_autoptr(GHashTable) seen = NULL; - g_autoptr(GArray) decoded = NULL; - gsize offset; - - g_assert (self != NULL); - g_assert (fd != -1); - - ar = g_byte_array_new (); - seen = g_hash_table_new (NULL, NULL); - decoded = g_array_new (FALSE, FALSE, sizeof (Decoded)); - - /* Add some empty space to both give us non-zero offsets and also ensure - * empty space between data. - */ - g_byte_array_append (ar, (guint8 *)&empty, sizeof empty); - - for (guint i = 0; i < self->samples->len; i++) - { - Element *ele = g_ptr_array_index (self->samples, i); - Decoded dec; - - if (begin == 0) - begin = ele->addr; - - if ((i + 1) < self->samples->len) - { - Element *next = g_ptr_array_index (self->samples, i + 1); - - if (ele->pid == next->pid && ele->name == next->name) - continue; - } - - dec.padding = 0; - dec.addr_begin = begin; - dec.addr_end = ele->addr; - dec.pid = ele->pid; - dec.offset = get_string_offset (ar, seen, ele->name); - - g_assert (!dec.offset || g_strcmp0 (ele->name, (gchar *)&ar->data[dec.offset]) == 0); - - if (ele->tag) - { - const gchar *tagstr = g_quark_to_string (ele->tag); - - dec.tag_offset = get_string_offset (ar, seen, tagstr); - g_assert (g_strcmp0 (tagstr, (gchar *)&ar->data[dec.tag_offset]) == 0); - } - else - dec.tag_offset = 0; - - g_array_append_val (decoded, dec); - - begin = 0; - } - - offset = sizeof (Decoded) * (gsize)decoded->len; - - for (guint i = 0; i < decoded->len; i++) - { - Decoded *dec = &g_array_index (decoded, Decoded, i); - - if (dec->offset > 0) - dec->offset += offset; - - if (dec->tag_offset > 0) - dec->tag_offset += offset; - } - - if (write (fd, decoded->data, offset) != offset) - return FALSE; - - if (write (fd, ar->data, ar->len) != ar->len) - return FALSE; - - /* Aggressively release state now that we're finished */ - if (self->samples->len) - g_ptr_array_remove_range (self->samples, 0, self->samples->len); - if (self->resolvers != NULL) - g_ptr_array_remove_range (self->resolvers, 0, self->resolvers->len); - g_string_chunk_clear (self->chunk); - g_hash_table_remove_all (self->lookasides); - - lseek (fd, 0L, SEEK_SET); - - return TRUE; -} - -gboolean -sysprof_symbol_map_deserialize (SysprofSymbolMap *self, - gint byte_order, - gint fd) -{ - g_autoptr(GError) error = NULL; - gboolean needs_swap = byte_order != G_BYTE_ORDER; - gchar *beginptr; - gchar *endptr; - - g_return_val_if_fail (self != NULL, FALSE); - g_return_val_if_fail (self->mapped == NULL, FALSE); - - if (!(self->mapped = g_mapped_file_new_from_fd (fd, TRUE, &error))) - { - g_warning ("Failed to map file: %s\n", error->message); - return FALSE; - } - - beginptr = g_mapped_file_get_contents (self->mapped); - endptr = beginptr + g_mapped_file_get_length (self->mapped); - - /* Ensure trialing \0 */ - if (endptr > beginptr) - *(endptr - 1) = 0; - - for (gchar *ptr = beginptr; - ptr < endptr && (ptr + sizeof (Decoded)) < endptr; - ptr += sizeof (Decoded)) - { - Decoded *sym = (Decoded *)ptr; - - if (sym->addr_begin == 0 && - sym->addr_end == 0 && - sym->pid == 0 && - sym->offset == 0) - { - self->symbols = (const Decoded *)beginptr; - self->n_symbols = sym - self->symbols; - break; - } - else if (needs_swap) - { - sym->addr_begin = GUINT64_SWAP_LE_BE (sym->addr_begin); - sym->addr_end = GUINT64_SWAP_LE_BE (sym->addr_end); - sym->pid = GUINT32_SWAP_LE_BE (sym->pid); - sym->offset = GUINT32_SWAP_LE_BE (sym->offset); - sym->tag_offset = GUINT32_SWAP_LE_BE (sym->tag_offset); - } - -#if 0 - g_print ("Added pid=%d begin=%p end=%p\n", - sym->pid, (gpointer)sym->addr_begin, (gpointer)sym->addr_end); -#endif - } - - self->beginptr = beginptr; - self->endptr = endptr; - - return TRUE; -} diff --git a/src/libsysprof/sysprof-symbol-map.h b/src/libsysprof/sysprof-symbol-map.h deleted file mode 100644 index 2e482c2c..00000000 --- a/src/libsysprof/sysprof-symbol-map.h +++ /dev/null @@ -1,49 +0,0 @@ -/* sysprof-symbol-map.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include - -#include "sysprof-symbol-resolver.h" - -G_BEGIN_DECLS - -typedef struct _SysprofSymbolMap SysprofSymbolMap; - -SysprofSymbolMap *sysprof_symbol_map_new (void); -void sysprof_symbol_map_add_resolver (SysprofSymbolMap *self, - SysprofSymbolResolver *resolver); -void sysprof_symbol_map_resolve (SysprofSymbolMap *self, - SysprofCaptureReader *reader); -const gchar *sysprof_symbol_map_lookup (SysprofSymbolMap *self, - gint64 time, - gint32 pid, - SysprofCaptureAddress addr, - GQuark *tag); -void sysprof_symbol_map_printf (SysprofSymbolMap *self); -gboolean sysprof_symbol_map_serialize (SysprofSymbolMap *self, - gint fd); -gboolean sysprof_symbol_map_deserialize (SysprofSymbolMap *self, - gint byte_order, - gint fd); -void sysprof_symbol_map_free (SysprofSymbolMap *self); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-symbol-private.h b/src/libsysprof/sysprof-symbol-private.h new file mode 100644 index 00000000..0e99f6c0 --- /dev/null +++ b/src/libsysprof/sysprof-symbol-private.h @@ -0,0 +1,119 @@ +/* + * sysprof-symbol-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-symbol.h" + +G_BEGIN_DECLS + +struct _SysprofSymbol +{ + GObject parent_instance; + + guint hash; + + GRefString *name; + GRefString *binary_path; + GRefString *binary_nick; + + SysprofAddress begin_address; + SysprofAddress end_address; + + guint kind : 3; + guint is_fallback : 1; +}; + +SysprofSymbol *_sysprof_symbol_new (GRefString *name, + GRefString *binary_path, + GRefString *binary_nick, + SysprofAddress begin_address, + SysprofAddress end_address, + SysprofSymbolKind kind); + +static inline SysprofSymbol * +_sysprof_symbol_copy (SysprofSymbol *self) +{ + SysprofSymbol *copy; + + copy = _sysprof_symbol_new (self->name ? g_ref_string_acquire (self->name) : NULL, + self->binary_path ? g_ref_string_acquire (self->binary_path) : NULL, + self->binary_nick ? g_ref_string_acquire (self->binary_nick) : NULL, + self->begin_address, + self->end_address, + self->kind); + + return copy; +} + +static inline gboolean +_sysprof_symbol_equal (const SysprofSymbol *a, + const SysprofSymbol *b) +{ + if (a == b) + return TRUE; + + if (a->hash != b->hash) + return FALSE; + + if (a->kind != b->kind) + return FALSE; + + if (a->name == NULL || b->name == NULL) + return FALSE; + + return strcmp (a->name, b->name) == 0; +} + +static inline gboolean +_sysprof_symbol_is_context_switch (SysprofSymbol *symbol) +{ + return symbol->kind == SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH; +} + +static inline gboolean +_sysprof_symbol_is_system_library (SysprofSymbol *symbol) +{ + switch (symbol->kind) + { + case SYSPROF_SYMBOL_KIND_ROOT: + case SYSPROF_SYMBOL_KIND_PROCESS: + case SYSPROF_SYMBOL_KIND_THREAD: + case SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH: + case SYSPROF_SYMBOL_KIND_UNWINDABLE: + default: + return FALSE; + + case SYSPROF_SYMBOL_KIND_KERNEL: + return TRUE; + + case SYSPROF_SYMBOL_KIND_USER: + if (symbol->binary_nick != NULL) + return TRUE; + + if (symbol->binary_path != NULL) + return !!strstr (symbol->binary_path, "/usr/"); + + return FALSE; + } +} + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbol-resolver.c b/src/libsysprof/sysprof-symbol-resolver.c deleted file mode 100644 index 3ba6166d..00000000 --- a/src/libsysprof/sysprof-symbol-resolver.c +++ /dev/null @@ -1,189 +0,0 @@ -/* sysprof-symbol-resolver.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include - -#include "sysprof-platform.h" -#include "sysprof-symbol-resolver.h" - -G_DEFINE_INTERFACE (SysprofSymbolResolver, sysprof_symbol_resolver, G_TYPE_OBJECT) - -static gchar * -sysprof_symbol_resolver_real_resolve (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofCaptureAddress address, - GQuark *tag) -{ - *tag = 0; - return NULL; -} - -static gchar * -sysprof_symbol_resolver_real_resolve_with_context (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag) -{ - *tag = 0; - - if (SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->resolve) - return SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->resolve (self, time, pid, address, tag); - - return NULL; -} - -static void -sysprof_symbol_resolver_default_init (SysprofSymbolResolverInterface *iface) -{ - iface->resolve = sysprof_symbol_resolver_real_resolve; - iface->resolve_with_context = sysprof_symbol_resolver_real_resolve_with_context; -} - -void -sysprof_symbol_resolver_load (SysprofSymbolResolver *self, - SysprofCaptureReader *reader) -{ - g_return_if_fail (SYSPROF_IS_SYMBOL_RESOLVER (self)); - g_return_if_fail (reader != NULL); - - if (SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->load) - SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->load (self, reader); -} - -/** - * sysprof_symbol_resolver_resolve: - * @self: A #SysprofSymbolResolver - * @time: The time of the sample - * @pid: The process generating the sample - * @address: the sample address - * @tag: (out): A tag for the symbol. - * - * Gets the symbol name for @address that was part of process @pid - * at @time. Optionally, you can set @tag to a quark describing the - * symbol. This can be used to provide a bit more information when - * rendering the treeview. You might choose to describe the library - * such as "GObject" or "GTK+" or "Linux" for the kernel. - * - * Returns: (nullable) (transfer full): A newly allocated string, or %NULL. - */ -gchar * -sysprof_symbol_resolver_resolve (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofCaptureAddress address, - GQuark *tag) -{ - GQuark dummy; - - g_return_val_if_fail (SYSPROF_IS_SYMBOL_RESOLVER (self), NULL); - - if (tag == NULL) - tag = &dummy; - - *tag = 0; - - if (SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->resolve) - return SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->resolve (self, time, pid, address, tag); - - return NULL; -} - -/** - * sysprof_symbol_resolver_resolve_with_context: - * @self: A #SysprofSymbolResolver - * @time: The time of the sample - * @pid: The process generating the sample - * @context: the address context - * @address: the sample address - * @tag: (out): A tag for the symbol. - * - * This function is like sysprof_symbol_resolver_resolve() but allows - * access to the address context, which might be necessary to - * determine the difference between user space and kernel space - * addresses. - * - * Returns: (nullable) (transfer full): A newly allocated string, or %NULL. - */ -gchar * -sysprof_symbol_resolver_resolve_with_context (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag) -{ - GQuark dummy; - - g_return_val_if_fail (SYSPROF_IS_SYMBOL_RESOLVER (self), NULL); - - if (tag == NULL) - tag = &dummy; - - *tag = 0; - - return SYSPROF_SYMBOL_RESOLVER_GET_IFACE (self)->resolve_with_context (self, time, pid, context, address, tag); -} - -char * -_sysprof_symbol_resolver_load_file (SysprofCaptureReader *reader, - const char *path) -{ - g_autofree char *data = NULL; - goffset len; - goffset pos = 0; - int fd; - - g_assert (reader != NULL); - g_assert (path != NULL); - - sysprof_capture_reader_reset (reader); - - if (-1 == (fd = sysprof_memfd_create ("")) || - !sysprof_capture_reader_read_file_fd (reader, path, fd)) - { - if (fd != -1) - close (fd); - return NULL; - } - - len = lseek (fd, 0L, SEEK_CUR); - data = g_malloc (len + 1); - lseek (fd, 0L, SEEK_SET); - - while (pos < len) - { - gssize n_read = read (fd, data + pos, len - pos); - - if (n_read < 0) - return NULL; - - pos += n_read; - } - - data[len] = 0; - close (fd); - - return g_steal_pointer (&data); -} diff --git a/src/libsysprof/sysprof-symbol-resolver.h b/src/libsysprof/sysprof-symbol-resolver.h deleted file mode 100644 index fcdceebb..00000000 --- a/src/libsysprof/sysprof-symbol-resolver.h +++ /dev/null @@ -1,76 +0,0 @@ -/* sysprof-symbol-resolver.h - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include - -#include "sysprof-address.h" -#include "sysprof-capture-reader.h" -#include "sysprof-version-macros.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_SYMBOL_RESOLVER (sysprof_symbol_resolver_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_INTERFACE (SysprofSymbolResolver, sysprof_symbol_resolver, SYSPROF, SYMBOL_RESOLVER, GObject) - -struct _SysprofSymbolResolverInterface -{ - GTypeInterface parent_interface; - - void (*load) (SysprofSymbolResolver *self, - SysprofCaptureReader *reader); - gchar *(*resolve) (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofCaptureAddress address, - GQuark *tag); - gchar *(*resolve_with_context) (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag); -}; - -SYSPROF_AVAILABLE_IN_ALL -void sysprof_symbol_resolver_load (SysprofSymbolResolver *self, - SysprofCaptureReader *reader); -SYSPROF_AVAILABLE_IN_ALL -gchar *sysprof_symbol_resolver_resolve (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofCaptureAddress address, - GQuark *tag); -SYSPROF_AVAILABLE_IN_ALL -gchar *sysprof_symbol_resolver_resolve_with_context (SysprofSymbolResolver *self, - guint64 time, - GPid pid, - SysprofAddressContext context, - SysprofCaptureAddress address, - GQuark *tag); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-symbol.c b/src/libsysprof/sysprof-symbol.c new file mode 100644 index 00000000..6db132a6 --- /dev/null +++ b/src/libsysprof/sysprof-symbol.c @@ -0,0 +1,240 @@ +/* + * sysprof-symbol.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-symbol-private.h" + +G_DEFINE_FINAL_TYPE (SysprofSymbol, sysprof_symbol, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_BINARY_NICK, + PROP_BINARY_PATH, + PROP_KIND, + PROP_NAME, + PROP_TOOLTIP_TEXT, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_symbol_finalize (GObject *object) +{ + SysprofSymbol *self = (SysprofSymbol *)object; + + g_clear_pointer (&self->name, g_ref_string_release); + g_clear_pointer (&self->binary_path, g_ref_string_release); + g_clear_pointer (&self->binary_nick, g_ref_string_release); + + G_OBJECT_CLASS (sysprof_symbol_parent_class)->finalize (object); +} + +static void +sysprof_symbol_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSymbol *self = SYSPROF_SYMBOL (object); + + switch (prop_id) + { + case PROP_NAME: + g_value_set_string (value, sysprof_symbol_get_name (self)); + break; + + case PROP_BINARY_NICK: + g_value_set_string (value, sysprof_symbol_get_binary_nick (self)); + break; + + case PROP_BINARY_PATH: + g_value_set_string (value, sysprof_symbol_get_binary_path (self)); + break; + + case PROP_KIND: + g_value_set_enum (value, sysprof_symbol_get_kind (self)); + break; + + case PROP_TOOLTIP_TEXT: + g_value_take_string (value, sysprof_symbol_dup_tooltip_text (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_symbol_class_init (SysprofSymbolClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_symbol_finalize; + object_class->get_property = sysprof_symbol_get_property; + + properties [PROP_NAME] = + g_param_spec_string ("name", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BINARY_NICK] = + g_param_spec_string ("binary-nick", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BINARY_PATH] = + g_param_spec_string ("binary-path", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TOOLTIP_TEXT] = + g_param_spec_string ("tooltip-text", NULL, NULL, + NULL, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_KIND] = + g_param_spec_enum ("kind", NULL, NULL, + SYSPROF_TYPE_SYMBOL_KIND, + SYSPROF_SYMBOL_KIND_USER, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_symbol_init (SysprofSymbol *self) +{ + self->kind = SYSPROF_SYMBOL_KIND_USER; +} + +const char * +sysprof_symbol_get_name (SysprofSymbol *self) +{ + g_return_val_if_fail (SYSPROF_IS_SYMBOL (self), NULL); + + return self->name; +} + +const char * +sysprof_symbol_get_binary_nick (SysprofSymbol *self) +{ + g_return_val_if_fail (SYSPROF_IS_SYMBOL (self), NULL); + + return self->binary_nick; +} + +const char * +sysprof_symbol_get_binary_path (SysprofSymbol *self) +{ + g_return_val_if_fail (SYSPROF_IS_SYMBOL (self), NULL); + + return self->binary_path; +} + +SysprofSymbol * +_sysprof_symbol_new (GRefString *name, + GRefString *binary_path, + GRefString *binary_nick, + SysprofAddress begin_address, + SysprofAddress end_address, + SysprofSymbolKind kind) +{ + SysprofSymbol *self; + + self = g_object_new (SYSPROF_TYPE_SYMBOL, NULL); + self->name = name; + self->binary_path = binary_path; + self->binary_nick = binary_nick; + self->begin_address = begin_address; + self->end_address = end_address; + self->hash = g_str_hash (name); + self->kind = kind; + + /* If we got a path for the symbol, add that to the hash so that we + * can be sure that we're working with a symbol in the same file when + * there are collisions. + * + * That way, we have a chance of joining symbols from different runtimes + * and/or containers, but only if they are reasonably the same ABI. + */ + if (binary_path != NULL) + { + const char *base = strrchr (binary_path, '/'); + + if (base != NULL) + self->hash ^= g_str_hash (base); + } + + if (binary_nick != NULL) + self->hash ^= g_str_hash (binary_nick); + + return self; +} + +gboolean +sysprof_symbol_equal (const SysprofSymbol *a, + const SysprofSymbol *b) +{ + return _sysprof_symbol_equal (a, b); +} + +guint +sysprof_symbol_hash (const SysprofSymbol *self) +{ + return self->hash; +} + +SysprofSymbolKind +sysprof_symbol_get_kind (SysprofSymbol *self) +{ + return self ? self->kind : 0; +} + +/** + * sysprof_symbol_dup_tooltip_text: + * @self: a #SysprofSymbol + * + * Returns: (transfer full): the tooltip text + */ +char * +sysprof_symbol_dup_tooltip_text (SysprofSymbol *self) +{ + GString *str; + + g_return_val_if_fail (SYSPROF_IS_SYMBOL (self), NULL); + + str = g_string_new (self->name); + + if (!g_str_has_prefix (str->str, "In File") && self->binary_path) + g_string_append_printf (str, " [%s+0x%"G_GINT64_MODIFIER"x]", self->binary_path, self->begin_address); + + return g_string_free (str, FALSE); +} + +G_DEFINE_ENUM_TYPE (SysprofSymbolKind, sysprof_symbol_kind, + G_DEFINE_ENUM_VALUE (SYSPROF_SYMBOL_KIND_ROOT, "root"), + G_DEFINE_ENUM_VALUE (SYSPROF_SYMBOL_KIND_PROCESS, "process"), + G_DEFINE_ENUM_VALUE (SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH, "context-switch"), + G_DEFINE_ENUM_VALUE (SYSPROF_SYMBOL_KIND_USER, "user"), + G_DEFINE_ENUM_VALUE (SYSPROF_SYMBOL_KIND_KERNEL, "kernel"), + G_DEFINE_ENUM_VALUE (SYSPROF_SYMBOL_KIND_UNWINDABLE, "unwindable")) diff --git a/src/libsysprof/sysprof-symbol.h b/src/libsysprof/sysprof-symbol.h new file mode 100644 index 00000000..7d70cc88 --- /dev/null +++ b/src/libsysprof/sysprof-symbol.h @@ -0,0 +1,65 @@ +/* + * sysprof-symbol.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +typedef enum _SysprofSymbolKind +{ + SYSPROF_SYMBOL_KIND_ROOT = 1, + SYSPROF_SYMBOL_KIND_PROCESS, + SYSPROF_SYMBOL_KIND_THREAD, + SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH, + SYSPROF_SYMBOL_KIND_USER, + SYSPROF_SYMBOL_KIND_KERNEL, + SYSPROF_SYMBOL_KIND_UNWINDABLE, +} SysprofSymbolKind; + +#define SYSPROF_TYPE_SYMBOL (sysprof_symbol_get_type()) +#define SYSPROF_TYPE_SYMBOL_KIND (sysprof_symbol_kind_get_type()) + +SYSPROF_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (SysprofSymbol, sysprof_symbol, SYSPROF, SYMBOL, GObject) + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_symbol_kind_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_symbol_get_name (SysprofSymbol *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_symbol_get_binary_nick (SysprofSymbol *self); +SYSPROF_AVAILABLE_IN_ALL +const char *sysprof_symbol_get_binary_path (SysprofSymbol *self); +SYSPROF_AVAILABLE_IN_ALL +SysprofSymbolKind sysprof_symbol_get_kind (SysprofSymbol *self); +SYSPROF_AVAILABLE_IN_ALL +guint sysprof_symbol_hash (const SysprofSymbol *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_symbol_equal (const SysprofSymbol *a, + const SysprofSymbol *b); +SYSPROF_AVAILABLE_IN_ALL +char *sysprof_symbol_dup_tooltip_text (SysprofSymbol *self); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbolizer-private.h b/src/libsysprof/sysprof-symbolizer-private.h new file mode 100644 index 00000000..dd917e44 --- /dev/null +++ b/src/libsysprof/sysprof-symbolizer-private.h @@ -0,0 +1,74 @@ +/* sysprof-symbolizer-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-address-layout-private.h" +#include "sysprof-document.h" +#include "sysprof-mount-namespace-private.h" +#include "sysprof-process-info-private.h" +#include "sysprof-strings-private.h" +#include "sysprof-symbol.h" +#include "sysprof-symbolizer.h" + +G_BEGIN_DECLS + +#define SYSPROF_SYMBOLIZER_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS(obj, SYSPROF_TYPE_SYMBOLIZER, SysprofSymbolizerClass) + +struct _SysprofSymbolizer +{ + GObject parent; +}; + +struct _SysprofSymbolizerClass +{ + GObjectClass parent_class; + + void (*prepare_async) (SysprofSymbolizer *self, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + gboolean (*prepare_finish) (SysprofSymbolizer *self, + GAsyncResult *result, + GError **error); + SysprofSymbol *(*symbolize) (SysprofSymbolizer *self, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address); +}; + + +void _sysprof_symbolizer_prepare_async (SysprofSymbolizer *self, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean _sysprof_symbolizer_prepare_finish (SysprofSymbolizer *self, + GAsyncResult *result, + GError **error); +SysprofSymbol *_sysprof_symbolizer_symbolize (SysprofSymbolizer *self, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbolizer.c b/src/libsysprof/sysprof-symbolizer.c new file mode 100644 index 00000000..9ad17ca2 --- /dev/null +++ b/src/libsysprof/sysprof-symbolizer.c @@ -0,0 +1,101 @@ +/* sysprof-symbolizer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-symbolizer-private.h" + +G_DEFINE_ABSTRACT_TYPE (SysprofSymbolizer, sysprof_symbolizer, G_TYPE_OBJECT) + +static void +sysprof_symbolizer_real_prepare_async (SysprofSymbolizer *self, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = g_task_new (self, cancellable, callback, user_data); + g_task_return_boolean (task, TRUE); +} + +static gboolean +sysprof_symbolizer_real_prepare_finish (SysprofSymbolizer *self, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +static void +sysprof_symbolizer_finalize (GObject *object) +{ + G_OBJECT_CLASS (sysprof_symbolizer_parent_class)->finalize (object); +} + +static void +sysprof_symbolizer_class_init (SysprofSymbolizerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_symbolizer_finalize; + + klass->prepare_async = sysprof_symbolizer_real_prepare_async; + klass->prepare_finish = sysprof_symbolizer_real_prepare_finish; +} + +static void +sysprof_symbolizer_init (SysprofSymbolizer *self) +{ +} + +void +_sysprof_symbolizer_prepare_async (SysprofSymbolizer *self, + SysprofDocument *document, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (SYSPROF_IS_SYMBOLIZER (self)); + g_return_if_fail (SYSPROF_IS_DOCUMENT (document)); + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + SYSPROF_SYMBOLIZER_GET_CLASS (self)->prepare_async (self, document, cancellable, callback, user_data); +} + +gboolean +_sysprof_symbolizer_prepare_finish (SysprofSymbolizer *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (SYSPROF_IS_SYMBOLIZER (self), FALSE); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + return SYSPROF_SYMBOLIZER_GET_CLASS (self)->prepare_finish (self, result, error); +} + +SysprofSymbol * +_sysprof_symbolizer_symbolize (SysprofSymbolizer *self, + SysprofStrings *strings, + const SysprofProcessInfo *process_info, + SysprofAddressContext context, + SysprofAddress address) +{ + return SYSPROF_SYMBOLIZER_GET_CLASS (self)->symbolize (self, strings, process_info, context, address); +} diff --git a/src/libsysprof/sysprof-symbolizer.h b/src/libsysprof/sysprof-symbolizer.h new file mode 100644 index 00000000..929007bc --- /dev/null +++ b/src/libsysprof/sysprof-symbolizer.h @@ -0,0 +1,42 @@ +/* sysprof-symbolizer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SYMBOLIZER (sysprof_symbolizer_get_type()) +#define SYSPROF_IS_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_SYMBOLIZER) +#define SYSPROF_SYMBOLIZER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_SYMBOLIZER, SysprofSymbolizer) +#define SYSPROF_SYMBOLIZER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_SYMBOLIZER, SysprofSymbolizerClass) + +typedef struct _SysprofSymbolizer SysprofSymbolizer; +typedef struct _SysprofSymbolizerClass SysprofSymbolizerClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_symbolizer_get_type (void) G_GNUC_CONST; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofSymbolizer, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbols-bundle.c b/src/libsysprof/sysprof-symbols-bundle.c new file mode 100644 index 00000000..ecb93603 --- /dev/null +++ b/src/libsysprof/sysprof-symbols-bundle.c @@ -0,0 +1,139 @@ +/* sysprof-symbols-bundle.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include +#include + +#include + +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" +#include "sysprof-symbols-bundle.h" + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureReader, sysprof_capture_reader_unref) + +static const DexAsyncPairInfo load_info = + DEX_ASYNC_PAIR_INFO_OBJECT (sysprof_document_loader_load_async, + sysprof_document_loader_load_finish); + +struct _SysprofSymbolsBundle +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofSymbolsBundleClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofSymbolsBundle, sysprof_symbols_bundle, SYSPROF_TYPE_INSTRUMENT) + +static DexFuture * +sysprof_symbols_bundle_augment_fiber (gpointer user_data) +{ + const DexAsyncPairInfo serialize_info = DEX_ASYNC_PAIR_INFO_BOXED ( + sysprof_document_serialize_symbols_async, + sysprof_document_serialize_symbols_finish, + G_TYPE_BYTES); + + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofSymbolizer) elf = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GBytes) bytes = NULL; + SysprofRecording *recording = user_data; + g_autoptr(GError) error = NULL; + g_autofd int fd = -1; + + g_assert (SYSPROF_IS_RECORDING (recording)); + + if (-1 == (fd = sysprof_recording_dup_fd (recording))) + return dex_future_new_for_errno (errno); + g_assert (fd > -1); + + if (!(loader = sysprof_document_loader_new_for_fd (fd, &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + g_assert (SYSPROF_IS_DOCUMENT_LOADER (loader)); + + /* Only symbolize ELF symbols as the rest can be symbolized + * by the application without having to resort to decoding. + */ + elf = sysprof_elf_symbolizer_new (); + sysprof_document_loader_set_symbolizer (loader, elf); + + if (!(document = dex_await_object (dex_async_pair_new (loader, &load_info), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + + if (!(bytes = dex_await_boxed (dex_async_pair_new (document, &serialize_info), &error))) + return dex_future_new_for_error (g_steal_pointer (&error)); + + _sysprof_recording_add_file_data (recording, + "__symbols__", + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + TRUE); + + return dex_future_new_for_boolean (TRUE); +} + +static DexFuture * +sysprof_symbols_bundle_augment (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + g_assert (SYSPROF_IS_SYMBOLS_BUNDLE (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + return dex_scheduler_spawn (NULL, 0, + sysprof_symbols_bundle_augment_fiber, + g_object_ref (recording), + g_object_unref); +} + +static void +sysprof_symbols_bundle_class_init (SysprofSymbolsBundleClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->augment = sysprof_symbols_bundle_augment; +} + +static void +sysprof_symbols_bundle_init (SysprofSymbolsBundle *self) +{ +} + +/** + * sysprof_symbols_bundle_new: + * @self: a #SysprofSymbolsBundle + * + * Creates a new instrment that will decode symbols at the end of a recording + * and attach them to the recording. + * + * Returns: (transfer full): a #SysprofSymbolsBundle + */ +SysprofInstrument * +sysprof_symbols_bundle_new (void) +{ + return g_object_new (SYSPROF_TYPE_SYMBOLS_BUNDLE, NULL); +} diff --git a/src/libsysprof/sysprof-symbols-bundle.h b/src/libsysprof/sysprof-symbols-bundle.h new file mode 100644 index 00000000..eb03a08e --- /dev/null +++ b/src/libsysprof/sysprof-symbols-bundle.h @@ -0,0 +1,42 @@ +/* sysprof-symbols-bundle.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SYMBOLS_BUNDLE (sysprof_symbols_bundle_get_type()) +#define SYSPROF_IS_SYMBOLS_BUNDLE(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_SYMBOLS_BUNDLE) +#define SYSPROF_SYMBOLS_BUNDLE(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_SYMBOLS_BUNDLE, SysprofSymbolsBundle) +#define SYSPROF_SYMBOLS_BUNDLE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_SYMBOLS_BUNDLE, SysprofSymbolsBundleClass) + +typedef struct _SysprofSymbolsBundle SysprofSymbolsBundle; +typedef struct _SysprofSymbolsBundleClass SysprofSymbolsBundleClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_symbols_bundle_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_symbols_bundle_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofSymbolsBundle, g_object_unref) + +G_END_DECLS diff --git a/src/libsysprof/sysprof-symbols-source.c b/src/libsysprof/sysprof-symbols-source.c deleted file mode 100644 index b4d7d49a..00000000 --- a/src/libsysprof/sysprof-symbols-source.c +++ /dev/null @@ -1,168 +0,0 @@ -/* sysprof-symbols-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-symbols-source" - -#include "config.h" - -#include "sysprof-elf-symbol-resolver.h" -#include "sysprof-private.h" -#include "sysprof-symbol-map.h" -#include "sysprof-symbols-source.h" - -#include "sysprof-platform.h" - -struct _SysprofSymbolsSource -{ - GObject parent_instance; - SysprofCaptureWriter *writer; - guint user_only : 1; -}; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofSymbolsSource, sysprof_symbols_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static void -sysprof_symbols_source_finalize (GObject *object) -{ - SysprofSymbolsSource *self = (SysprofSymbolsSource *)object; - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - - G_OBJECT_CLASS (sysprof_symbols_source_parent_class)->finalize (object); -} - -static void -sysprof_symbols_source_class_init (SysprofSymbolsSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_symbols_source_finalize; -} - -static void -sysprof_symbols_source_init (SysprofSymbolsSource *self) -{ -} - -static void -sysprof_symbols_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofSymbolsSource *self = (SysprofSymbolsSource *)source; - - g_assert (SYSPROF_IS_SYMBOLS_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&self->writer, sysprof_capture_writer_unref); - self->writer = sysprof_capture_writer_ref (writer); -} - -static void -sysprof_symbols_source_supplement (SysprofSource *source, - SysprofCaptureReader *reader) -{ - SysprofSymbolsSource *self = (SysprofSymbolsSource *)source; - g_autoptr(SysprofSymbolResolver) native = NULL; - g_autoptr(SysprofSymbolResolver) kernel = NULL; - SysprofSymbolMap *map; - gint fd; - - g_assert (SYSPROF_IS_SYMBOLS_SOURCE (self)); - g_assert (reader != NULL); - g_assert (self->writer != NULL); - - if (-1 == (fd = sysprof_memfd_create ("[sysprof-decode]"))) - return; - - map = sysprof_symbol_map_new (); - - native = sysprof_elf_symbol_resolver_new (); - sysprof_symbol_map_add_resolver (map, native); - - if (!self->user_only) - { - kernel = sysprof_kernel_symbol_resolver_new (); - sysprof_symbol_map_add_resolver (map, kernel); - } - - sysprof_symbol_map_resolve (map, reader); - sysprof_symbol_map_serialize (map, fd); - sysprof_symbol_map_free (map); - - sysprof_capture_writer_add_file_fd (self->writer, - SYSPROF_CAPTURE_CURRENT_TIME, - -1, - -1, - "__symbols__", - fd); - - close (fd); -} - -static void -sysprof_symbols_source_start (SysprofSource *source) -{ - g_assert (SYSPROF_IS_SYMBOLS_SOURCE (source)); - - sysprof_source_emit_ready (source); -} - -static void -sysprof_symbols_source_stop (SysprofSource *source) -{ - g_assert (SYSPROF_IS_SYMBOLS_SOURCE (source)); - - sysprof_source_emit_finished (source); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->set_writer = sysprof_symbols_source_set_writer; - iface->supplement = sysprof_symbols_source_supplement; - iface->start = sysprof_symbols_source_start; - iface->stop = sysprof_symbols_source_stop; -} - -SysprofSource * -sysprof_symbols_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_SYMBOLS_SOURCE, NULL); -} - -void -sysprof_symbols_source_set_user_only (SysprofSymbolsSource *self, - gboolean user_only) -{ - g_return_if_fail (SYSPROF_IS_SYMBOLS_SOURCE (self)); - - self->user_only = !!user_only; -} - -gboolean -sysprof_symbols_source_get_user_only (SysprofSymbolsSource *self) -{ - g_return_val_if_fail (SYSPROF_IS_SYMBOLS_SOURCE (self), FALSE); - - return self->user_only; -} diff --git a/src/libsysprof/sysprof-symbols-source.h b/src/libsysprof/sysprof-symbols-source.h deleted file mode 100644 index c78bb3c1..00000000 --- a/src/libsysprof/sysprof-symbols-source.h +++ /dev/null @@ -1,40 +0,0 @@ -/* sysprof-symbols-source.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "sysprof-source.h" - -G_BEGIN_DECLS - -#define SYSPROF_TYPE_SYMBOLS_SOURCE (sysprof_symbols_source_get_type()) - -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofSymbolsSource, sysprof_symbols_source, SYSPROF, SYMBOLS_SOURCE, GObject) - -SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_symbols_source_new (void); -SYSPROF_AVAILABLE_IN_3_36 -void sysprof_symbols_source_set_user_only (SysprofSymbolsSource *self, - gboolean user_only); -SYSPROF_AVAILABLE_IN_3_36 -gboolean sysprof_symbols_source_get_user_only (SysprofSymbolsSource *self); - -G_END_DECLS diff --git a/src/libsysprof/sysprof-system-logs.c b/src/libsysprof/sysprof-system-logs.c new file mode 100644 index 00000000..6652aba7 --- /dev/null +++ b/src/libsysprof/sysprof-system-logs.c @@ -0,0 +1,194 @@ +/* sysprof-system-logs.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include +#include + +#include "sysprof-system-logs.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +#ifdef HAVE_LIBSYSTEMD +# include "sysprof-journald-source.h" +#endif + +struct _SysprofSystemLogs +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofSystemLogsClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofSystemLogs, sysprof_system_logs, SYSPROF_TYPE_INSTRUMENT) + +#ifdef HAVE_LIBSYSTEMD +static char * +journal_get_data (sd_journal *journal, + const char *field) +{ + gconstpointer data = NULL; + const char *endptr; + const char *eq; + gsize length; + int ret; + + g_assert (journal != NULL); + g_assert (field != NULL); + + if ((ret = sd_journal_get_data (journal, field, &data, &length)) < 0) + return NULL; + + endptr = (const char *)data + length; + if (!(eq = memchr (data, '=', length))) + return NULL; + + eq++; + + return g_strndup (eq, endptr - eq); +} + +static gboolean +sysprof_system_logs_callback (SysprofCaptureWriter *writer, + sd_journal *journal) +{ + g_autofree char *message = NULL; + g_autofree char *priority = NULL; + sd_id128_t boot_id; + guint64 usec; + int severity = 0; + + g_assert (journal != NULL); + g_assert (writer != NULL); + + if (sd_journal_get_monotonic_usec (journal, &usec, &boot_id) != 0 || + !(message = journal_get_data (journal, "MESSAGE"))) + return G_SOURCE_CONTINUE; + + priority = journal_get_data (journal, "PRIORITY"); + + if (priority != NULL) + { + switch (priority[0]) + { + case '0': + case '1': + severity = G_LOG_LEVEL_ERROR; + break; + + case '2': + severity = G_LOG_LEVEL_CRITICAL; + break; + + case '3': + case '4': + severity = G_LOG_LEVEL_WARNING; + break; + + default: + case '5': + severity = G_LOG_LEVEL_MESSAGE; + break; + + case '6': + severity = G_LOG_LEVEL_INFO; + break; + + case '7': + severity = G_LOG_LEVEL_DEBUG; + break; + } + } + + sysprof_capture_writer_add_log (writer, usec*1000, -1, -1, severity, "System Log", message); + + return G_SOURCE_CONTINUE; +} +#endif + +#ifdef HAVE_LIBSYSTEMD +static DexFuture * +sysprof_system_logs_record_finished (DexFuture *future, + gpointer user_data) +{ + GSource *source = user_data; + g_source_destroy (source); + return dex_future_new_for_boolean (TRUE); +} +#endif + +static DexFuture * +sysprof_system_logs_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ +#ifdef HAVE_LIBSYSTEMD + SysprofCaptureWriter *writer; + GSource *source; + + g_assert (SYSPROF_IS_SYSTEM_LOGS (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + writer = _sysprof_recording_writer (recording); + + source = sysprof_journald_source_new ((SysprofJournaldSourceFunc)sysprof_system_logs_callback, + sysprof_capture_writer_ref (writer), + (GDestroyNotify)sysprof_capture_writer_unref); + g_source_set_static_name (source, "[sysprof-journald]"); + g_source_set_priority (source, G_PRIORITY_LOW); + g_source_attach (source, NULL); + + return dex_future_finally (dex_cancellable_new_from_cancellable (cancellable), + sysprof_system_logs_record_finished, + source, + (GDestroyNotify)g_source_unref); +#else + _sysprof_recording_diagnostic (recording, + _("System Logs"), + _("Recording system logs is not supported on your platform.")); + return dex_future_new_for_boolean (TRUE); +#endif +} + +static void +sysprof_system_logs_class_init (SysprofSystemLogsClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_system_logs_record; +} + +static void +sysprof_system_logs_init (SysprofSystemLogs *self) +{ +} + +SysprofInstrument * +sysprof_system_logs_new (void) +{ + return g_object_new (SYSPROF_TYPE_SYSTEM_LOGS, NULL); +} diff --git a/src/libsysprof/sysprof-system-logs.h b/src/libsysprof/sysprof-system-logs.h new file mode 100644 index 00000000..1739f722 --- /dev/null +++ b/src/libsysprof/sysprof-system-logs.h @@ -0,0 +1,43 @@ +/* sysprof-system-logs.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SYSTEM_LOGS (sysprof_system_logs_get_type()) +#define SYSPROF_IS_SYSTEM_LOGS(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_SYSTEM_LOGS) +#define SYSPROF_SYSTEM_LOGS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_SYSTEM_LOGS, SysprofSystemLogs) +#define SYSPROF_SYSTEM_LOGS_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_SYSTEM_LOGS, SysprofSystemLogsClass) + +typedef struct _SysprofSystemLogs SysprofSystemLogs; +typedef struct _SysprofSystemLogsClass SysprofSystemLogsClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_system_logs_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_system_logs_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofSystemLogs, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof/sysprof-thread-info.c b/src/libsysprof/sysprof-thread-info.c new file mode 100644 index 00000000..bf8d932c --- /dev/null +++ b/src/libsysprof/sysprof-thread-info.c @@ -0,0 +1,184 @@ +/* sysprof-thread-info.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-thread-info.h" + +struct _SysprofThreadInfo +{ + GObject parent_instance; + SysprofDocumentProcess *process; + int thread_id; +}; + +enum { + PROP_0, + PROP_PROCESS, + PROP_THREAD_ID, + PROP_IS_MAIN_THREAD, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofThreadInfo, sysprof_thread_info, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_thread_info_finalize (GObject *object) +{ + SysprofThreadInfo *self = (SysprofThreadInfo *)object; + + g_clear_object (&self->process); + + G_OBJECT_CLASS (sysprof_thread_info_parent_class)->finalize (object); +} + +static void +sysprof_thread_info_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofThreadInfo *self = SYSPROF_THREAD_INFO (object); + + switch (prop_id) + { + case PROP_IS_MAIN_THREAD: + g_value_set_boolean (value, sysprof_thread_info_is_main_thread (self)); + break; + + case PROP_PROCESS: + g_value_set_object (value, sysprof_thread_info_get_process (self)); + break; + + case PROP_THREAD_ID: + g_value_set_int (value, sysprof_thread_info_get_thread_id (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_thread_info_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofThreadInfo *self = SYSPROF_THREAD_INFO (object); + + switch (prop_id) + { + case PROP_PROCESS: + self->process = g_value_dup_object (value); + break; + + case PROP_THREAD_ID: + self->thread_id = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_thread_info_class_init (SysprofThreadInfoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_thread_info_finalize; + object_class->get_property = sysprof_thread_info_get_property; + object_class->set_property = sysprof_thread_info_set_property; + + properties[PROP_IS_MAIN_THREAD] = + g_param_spec_boolean ("is-main-thread", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_PROCESS] = + g_param_spec_object ("process", NULL, NULL, + SYSPROF_TYPE_DOCUMENT_PROCESS, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_THREAD_ID] = + g_param_spec_int ("thread-id", NULL, NULL, + G_MININT, G_MAXINT, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_thread_info_init (SysprofThreadInfo *self) +{ +} + +/** + * sysprof_thread_info_get_process: + * @self: a #SysprofThreadInfo + * + * Gets the process that owns the thread info. + * + * Returns: (transfer none): a #SysprofDocumentProcess + */ +SysprofDocumentProcess * +sysprof_thread_info_get_process (SysprofThreadInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_THREAD_INFO (self), NULL); + + return self->process; +} + +/** + * sysprof_thread_info_get_thread_id: + * @self: a #SysprofThreadInfo + * + * Gets the thread identifier. + * + * This typically matches what `gettid()` syscall returns on Linux. + * + * Returns: an integer containing the thread-id + */ +int +sysprof_thread_info_get_thread_id (SysprofThreadInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_THREAD_INFO (self), -1); + + return self->thread_id; +} + +/** + * sysprof_thread_info_is_main_thread: + * @self: a #SysprofThreadInfo + * + * Checks if the thread is the main thread for a process. + * + * Returns: %TRUE if the thread-id is the main thread. + */ +gboolean +sysprof_thread_info_is_main_thread (SysprofThreadInfo *self) +{ + g_return_val_if_fail (SYSPROF_IS_THREAD_INFO (self), FALSE); + + return self->thread_id == sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (self->process)); +} diff --git a/src/libsysprof/sysprof-battery-source.h b/src/libsysprof/sysprof-thread-info.h similarity index 57% rename from src/libsysprof/sysprof-battery-source.h rename to src/libsysprof/sysprof-thread-info.h index 468169f6..705949c9 100644 --- a/src/libsysprof/sysprof-battery-source.h +++ b/src/libsysprof/sysprof-thread-info.h @@ -1,6 +1,6 @@ -/* sysprof-battery-source.h +/* sysprof-thread-info.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,16 +20,20 @@ #pragma once -#include "sysprof-source.h" +#include "sysprof-document-process.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_BATTERY_SOURCE (sysprof_battery_source_get_type()) +#define SYSPROF_TYPE_THREAD_INFO (sysprof_thread_info_get_type()) SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofBatterySource, sysprof_battery_source, SYSPROF, BATTERY_SOURCE, GObject) +G_DECLARE_FINAL_TYPE (SysprofThreadInfo, sysprof_thread_info, SYSPROF, THREAD_INFO, GObject) SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_battery_source_new (void); +SysprofDocumentProcess *sysprof_thread_info_get_process (SysprofThreadInfo *self); +SYSPROF_AVAILABLE_IN_ALL +int sysprof_thread_info_get_thread_id (SysprofThreadInfo *self); +SYSPROF_AVAILABLE_IN_ALL +gboolean sysprof_thread_info_is_main_thread (SysprofThreadInfo *self); G_END_DECLS diff --git a/src/libsysprof/sysprof-time-span.c b/src/libsysprof/sysprof-time-span.c new file mode 100644 index 00000000..ef18d93f --- /dev/null +++ b/src/libsysprof/sysprof-time-span.c @@ -0,0 +1,40 @@ +/* sysprof-time-span.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-time-span.h" + +G_DEFINE_BOXED_TYPE (SysprofTimeSpan, sysprof_time_span, sysprof_time_span_copy, sysprof_time_span_free) + +SysprofTimeSpan * +sysprof_time_span_copy (const SysprofTimeSpan *self) +{ + if (self == NULL) + return NULL; + + return g_memdup2 (self, sizeof *self); +} + +void +sysprof_time_span_free (SysprofTimeSpan *self) +{ + g_free (self); +} diff --git a/src/libsysprof/sysprof-time-span.h b/src/libsysprof/sysprof-time-span.h new file mode 100644 index 00000000..a85595a8 --- /dev/null +++ b/src/libsysprof/sysprof-time-span.h @@ -0,0 +1,146 @@ +/* sysprof-time-span.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_SPAN (sysprof_time_span_get_type()) + +typedef struct _SysprofTimeSpan +{ + gint64 begin_nsec; + gint64 end_nsec; +} SysprofTimeSpan; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_time_span_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofTimeSpan *sysprof_time_span_copy (const SysprofTimeSpan *self); +SYSPROF_AVAILABLE_IN_ALL +void sysprof_time_span_free (SysprofTimeSpan *self); + +static inline gint64 +sysprof_time_span_duration (SysprofTimeSpan time_span) +{ + return time_span.end_nsec - time_span.begin_nsec; +} + +static inline SysprofTimeSpan +sysprof_time_span_relative_to (SysprofTimeSpan time_span, + gint64 point) +{ + return (SysprofTimeSpan) { + time_span.begin_nsec - point, + time_span.end_nsec - point + }; +} + +static inline void +sysprof_time_span_normalize (SysprofTimeSpan time_span, + SysprofTimeSpan allowed, + float values[restrict 2]) +{ + double duration = allowed.end_nsec - allowed.begin_nsec; + + time_span = sysprof_time_span_relative_to (time_span, allowed.begin_nsec); + + values[0] = time_span.begin_nsec / duration; + values[1] = time_span.end_nsec / duration; +} + +static inline SysprofTimeSpan +sysprof_time_span_order (SysprofTimeSpan time_span) +{ + if (time_span.begin_nsec > time_span.end_nsec) + return (SysprofTimeSpan) { time_span.end_nsec, time_span.begin_nsec }; + + return time_span; +} + +static inline gboolean +sysprof_time_span_clamp (SysprofTimeSpan *time_span, + SysprofTimeSpan allowed) +{ + if (time_span->end_nsec <= allowed.begin_nsec || + time_span->begin_nsec >= allowed.end_nsec) + { + time_span->begin_nsec = time_span->end_nsec = 0; + return FALSE; + } + + if (time_span->begin_nsec < allowed.begin_nsec) + time_span->begin_nsec = allowed.begin_nsec; + + if (time_span->end_nsec > allowed.end_nsec) + time_span->end_nsec = allowed.end_nsec; + + return TRUE; +} + +static inline char * +sysprof_time_offset_to_string (gint64 o) +{ + char str[32]; + + if (o == 0) + g_snprintf (str, sizeof str, "%.3lfs", .0); + else if (o < 1000000) + g_snprintf (str, sizeof str, "%.3lfμs", o/1000.); + else if (o < SYSPROF_NSEC_PER_SEC) + g_snprintf (str, sizeof str, "%.3lfms", o/1000000.); + else + g_snprintf (str, sizeof str, "%.3lfs", o/(double)SYSPROF_NSEC_PER_SEC); + + return g_strdup (str); +} + +static inline char * +sysprof_time_span_to_string (const SysprofTimeSpan *span) +{ + g_autofree char *begin = sysprof_time_offset_to_string (span->begin_nsec); + g_autofree char *end = NULL; + + if (span->end_nsec == span->begin_nsec) + return g_steal_pointer (&begin); + + end = sysprof_time_offset_to_string (span->end_nsec - span->begin_nsec); + + return g_strdup_printf ("%s (%s)", begin, end); +} + +static inline gboolean +sysprof_time_span_equal (const SysprofTimeSpan *a, + const SysprofTimeSpan *b) +{ + if (a == b) + return TRUE; + + if (a == NULL || b == NULL) + return FALSE; + + return memcmp (a, b, sizeof *a) == 0; +} + +G_END_DECLS diff --git a/src/libsysprof/sysprof-tracefd-source.c b/src/libsysprof/sysprof-tracefd-source.c deleted file mode 100644 index 72c9fb05..00000000 --- a/src/libsysprof/sysprof-tracefd-source.c +++ /dev/null @@ -1,302 +0,0 @@ -/* sysprof-tracefd-source.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-tracefd-source" - -#include "config.h" - -#include -#include - -#include "sysprof-capture-autocleanups.h" -#include "sysprof-platform.h" -#include "sysprof-tracefd-source.h" - -typedef struct -{ - SysprofCaptureWriter *writer; - gchar *envvar; - gint tracefd; -} SysprofTracefdSourcePrivate; - -enum { - PROP_0, - PROP_ENVVAR, - N_PROPS -}; - -static void source_iface_init (SysprofSourceInterface *iface); - -G_DEFINE_TYPE_WITH_CODE (SysprofTracefdSource, sysprof_tracefd_source, G_TYPE_OBJECT, - G_ADD_PRIVATE (SysprofTracefdSource) - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, source_iface_init)) - -static GParamSpec *properties [N_PROPS]; - -static void -sysprof_tracefd_source_finalize (GObject *object) -{ - SysprofTracefdSource *self = (SysprofTracefdSource *)object; - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - g_clear_pointer (&priv->writer, sysprof_capture_writer_unref); - g_clear_pointer (&priv->envvar, g_free); - - if (priv->tracefd != -1) - { - close (priv->tracefd); - priv->tracefd = -1; - } - - G_OBJECT_CLASS (sysprof_tracefd_source_parent_class)->finalize (object); -} - -static void -sysprof_tracefd_source_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) -{ - SysprofTracefdSource *self = SYSPROF_TRACEFD_SOURCE (object); - - switch (prop_id) - { - case PROP_ENVVAR: - g_value_set_string (value, sysprof_tracefd_source_get_envvar (self)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_tracefd_source_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) -{ - SysprofTracefdSource *self = SYSPROF_TRACEFD_SOURCE (object); - - switch (prop_id) - { - case PROP_ENVVAR: - sysprof_tracefd_source_set_envvar (self, g_value_get_string (value)); - break; - - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - } -} - -static void -sysprof_tracefd_source_class_init (SysprofTracefdSourceClass *klass) -{ - GObjectClass *object_class = G_OBJECT_CLASS (klass); - - object_class->finalize = sysprof_tracefd_source_finalize; - object_class->get_property = sysprof_tracefd_source_get_property; - object_class->set_property = sysprof_tracefd_source_set_property; - - properties [PROP_ENVVAR] = - g_param_spec_string ("envvar", - "Environment Variable", - "The environment variable to set", - "SYSPROF_TRACE_FD", - (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); - - g_object_class_install_properties (object_class, N_PROPS, properties); -} - -static void -sysprof_tracefd_source_init (SysprofTracefdSource *self) -{ - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - priv->tracefd = -1; - priv->envvar = g_strdup ("SYSPROF_TRACE_FD"); -} - -const gchar * -sysprof_tracefd_source_get_envvar (SysprofTracefdSource *self) -{ - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - g_return_val_if_fail (SYSPROF_IS_TRACEFD_SOURCE (self), NULL); - - return priv->envvar; -} - -void -sysprof_tracefd_source_set_envvar (SysprofTracefdSource *self, - const gchar *envvar) -{ - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - g_return_if_fail (SYSPROF_IS_TRACEFD_SOURCE (self)); - - if (envvar == NULL) - envvar = "SYSPROF_TRACE_FD"; - - if (g_strcmp0 (priv->envvar, envvar) != 0) - { - g_free (priv->envvar); - priv->envvar = g_strdup (envvar); - g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ENVVAR]); - } -} - -static void -sysprof_tracefd_source_modify_spawn (SysprofSource *source, - SysprofSpawnable *spawnable) -{ - SysprofTracefdSource *self = (SysprofTracefdSource *)source; - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - g_autofree gchar *name = NULL; - g_autofree gchar *fdstr = NULL; - gint dest_fd; - gint fd; - - g_assert (SYSPROF_IS_TRACEFD_SOURCE (self)); - g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); - g_assert (priv->tracefd == -1); - - name = g_strdup_printf ("[sysprof-tracefd:%s]", priv->envvar); - - if (-1 == (fd = sysprof_memfd_create (name))) - { - g_warning ("Failed to create FD for tracefd capture: %s", - g_strerror (errno)); - return; - } - - if (-1 == (priv->tracefd = dup (fd))) - { - g_warning ("Failed to duplicate tracefd for readback: %s", - g_strerror (errno)); - close (fd); - return; - } - - dest_fd = sysprof_spawnable_take_fd (spawnable, fd, -1); - fdstr = g_strdup_printf ("%u", dest_fd); - sysprof_spawnable_setenv (spawnable, priv->envvar, fdstr); -} - -static void -sysprof_tracefd_source_serialize (SysprofSource *source, - GKeyFile *keyfile, - const gchar *group) -{ - SysprofTracefdSource *self = (SysprofTracefdSource *)source; - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - g_assert (SYSPROF_IS_TRACEFD_SOURCE (self)); - g_assert (keyfile != NULL); - g_assert (group != NULL); - - g_key_file_set_string (keyfile, group, "envvar", priv->envvar); -} - -static void -sysprof_tracefd_source_deserialize (SysprofSource *source, - GKeyFile *keyfile, - const gchar *group) -{ - SysprofTracefdSource *self = (SysprofTracefdSource *)source; - g_autofree gchar *envvar = NULL; - - g_assert (SYSPROF_IS_TRACEFD_SOURCE (self)); - g_assert (keyfile != NULL); - g_assert (group != NULL); - - if ((envvar = g_key_file_get_string (keyfile, group, "envvar", NULL))) - sysprof_tracefd_source_set_envvar (self, envvar); -} - -static void -sysprof_tracefd_source_prepare (SysprofSource *source) -{ - g_assert (SYSPROF_IS_TRACEFD_SOURCE (source)); - - sysprof_source_emit_ready (source); -} - -static gboolean -sysprof_tracefd_source_get_is_ready (SysprofSource *source) -{ - g_assert (SYSPROF_IS_TRACEFD_SOURCE (source)); - - return TRUE; -} - -static void -sysprof_tracefd_source_stop (SysprofSource *source) -{ - SysprofTracefdSource *self = (SysprofTracefdSource *)source; - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - g_assert (SYSPROF_IS_TRACEFD_SOURCE (self)); - - if (priv->writer != NULL && priv->tracefd != -1) - { - g_autoptr(SysprofCaptureReader) reader = NULL; - - /* FIXME: Error handling is ignored here */ - if ((reader = sysprof_capture_reader_new_from_fd (priv->tracefd))) - sysprof_capture_writer_cat (priv->writer, reader); - - priv->tracefd = -1; - } - - sysprof_source_emit_finished (source); -} - -static void -sysprof_tracefd_source_set_writer (SysprofSource *source, - SysprofCaptureWriter *writer) -{ - SysprofTracefdSource *self = (SysprofTracefdSource *)source; - SysprofTracefdSourcePrivate *priv = sysprof_tracefd_source_get_instance_private (self); - - g_assert (SYSPROF_IS_TRACEFD_SOURCE (self)); - g_assert (writer != NULL); - - g_clear_pointer (&priv->writer, sysprof_capture_writer_unref); - priv->writer = sysprof_capture_writer_ref (writer); -} - -static void -source_iface_init (SysprofSourceInterface *iface) -{ - iface->deserialize = sysprof_tracefd_source_deserialize; - iface->get_is_ready = sysprof_tracefd_source_get_is_ready; - iface->modify_spawn = sysprof_tracefd_source_modify_spawn; - iface->prepare = sysprof_tracefd_source_prepare; - iface->serialize = sysprof_tracefd_source_serialize; - iface->set_writer = sysprof_tracefd_source_set_writer; - iface->stop = sysprof_tracefd_source_stop; -} - -SysprofSource * -sysprof_tracefd_source_new (void) -{ - return g_object_new (SYSPROF_TYPE_TRACEFD_SOURCE, NULL); -} diff --git a/src/libsysprof/sysprof-tracer.c b/src/libsysprof/sysprof-tracer.c new file mode 100644 index 00000000..8943d0b8 --- /dev/null +++ b/src/libsysprof/sysprof-tracer.c @@ -0,0 +1,88 @@ +/* sysprof-tracer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" +#include "sysprof-tracer.h" + +/** + * SysprofTracer: + * + * The `SysprofTracer` instrument provides integration with GCC's + * `-finstrument-functions`. It uses an `LD_PRELOAD` on Linux to record + * stacktraces on `__cyg_profile_func_enter` and `__cyg_profile_func_exit`. + * + * On macOS, `profile_func_enter` and `profile_func_exit` are used. + */ + +struct _SysprofTracer +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofTracerClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofTracer, sysprof_tracer, SYSPROF_TYPE_INSTRUMENT) + +static DexFuture * +sysprof_tracer_prepare (SysprofInstrument *instrument, + SysprofRecording *recording) +{ + SysprofSpawnable *spawnable; + + g_assert (SYSPROF_IS_INSTRUMENT (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + + if ((spawnable = _sysprof_recording_get_spawnable (recording))) + sysprof_spawnable_add_ld_preload (spawnable, + PACKAGE_LIBDIR"/libsysprof-tracer-" API_VERSION_S ".so"); + else + _sysprof_recording_diagnostic (recording, + _("Tracer"), + _("Tracing requires spawning a program compiled with ‘-finstrument-functions’. Tracing will not be available.")); + + return dex_future_new_for_boolean (TRUE); +} + +static void +sysprof_tracer_class_init (SysprofTracerClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->prepare = sysprof_tracer_prepare; +} + +static void +sysprof_tracer_init (SysprofTracer *self) +{ +} + +SysprofInstrument * +sysprof_tracer_new (void) +{ + return g_object_new (SYSPROF_TYPE_TRACER, NULL); +} diff --git a/src/libsysprof/sysprof-memory-source.h b/src/libsysprof/sysprof-tracer.h similarity index 51% rename from src/libsysprof/sysprof-memory-source.h rename to src/libsysprof/sysprof-tracer.h index 8e70e45a..f7966fbd 100644 --- a/src/libsysprof/sysprof-memory-source.h +++ b/src/libsysprof/sysprof-tracer.h @@ -1,6 +1,6 @@ -/* sysprof-memory-source.h +/* sysprof-tracer.h * - * Copyright 2018-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,21 +20,23 @@ #pragma once -#if !defined (SYSPROF_INSIDE) && !defined (SYSPROF_COMPILATION) -# error "Only can be included directly." -#endif - -#include "sysprof-source.h" -#include "sysprof-version-macros.h" +#include "sysprof-instrument.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_MEMORY_SOURCE (sysprof_memory_source_get_type()) +#define SYSPROF_TYPE_TRACER (sysprof_tracer_get_type()) +#define SYSPROF_IS_TRACER(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_TRACER) +#define SYSPROF_TRACER(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_TRACER, SysprofTracer) +#define SYSPROF_TRACER_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_TRACER, SysprofTracerClass) + +typedef struct _SysprofTracer SysprofTracer; +typedef struct _SysprofTracerClass SysprofTracerClass; SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofMemorySource, sysprof_memory_source, SYSPROF, MEMORY_SOURCE, GObject) - +GType sysprof_tracer_get_type (void) G_GNUC_CONST; SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_memory_source_new (void); +SysprofInstrument *sysprof_tracer_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofTracer, g_object_unref) G_END_DECLS diff --git a/src/libsysprof/sysprof.h b/src/libsysprof/sysprof.h index ac96871b..b284e7e4 100644 --- a/src/libsysprof/sysprof.h +++ b/src/libsysprof/sysprof.h @@ -1,6 +1,6 @@ /* sysprof.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,56 +14,74 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include -#include G_BEGIN_DECLS #define SYSPROF_INSIDE - -# include "sysprof-battery-source.h" -# include "sysprof-callgraph-profile.h" -# include "sysprof-capture-autocleanups.h" -# include "sysprof-capture-frame-object.h" -# include "sysprof-capture-gobject.h" -# include "sysprof-capture-model.h" -# include "sysprof-capture-symbol-resolver.h" -# include "sysprof-control-source.h" -# include "sysprof-diskstat-source.h" -# include "sysprof-elf-symbol-resolver.h" -# include "sysprof-gjs-source.h" -# include "sysprof-governor-source.h" -# include "sysprof-jitmap-symbol-resolver.h" -# include "sysprof-kernel-symbol-resolver.h" -# include "sysprof-kernel-symbol.h" -# include "sysprof-local-profiler.h" -# include "sysprof-memprof-profile.h" -# include "sysprof-memprof-source.h" -# include "sysprof-netdev-source.h" -# include "sysprof-preload-source.h" -# include "sysprof-process-model-item.h" -# include "sysprof-process-model.h" -# include "sysprof-profile.h" +# include "sysprof-battery-charge.h" +# include "sysprof-bundled-symbolizer.h" +# include "sysprof-callgraph-frame.h" +# include "sysprof-callgraph-symbol.h" +# include "sysprof-callgraph.h" +# include "sysprof-category-summary.h" +# include "sysprof-cpu-info.h" +# include "sysprof-cpu-usage.h" +# include "sysprof-diagnostic.h" +# include "sysprof-disk-usage.h" +# include "sysprof-document-allocation.h" +# include "sysprof-document-counter-value.h" +# include "sysprof-document-counter.h" +# include "sysprof-document-ctrdef.h" +# include "sysprof-document-ctrset.h" +# include "sysprof-document-exit.h" +# include "sysprof-document-file-chunk.h" +# include "sysprof-document-file.h" +# include "sysprof-document-fork.h" +# include "sysprof-document-frame.h" +# include "sysprof-document-jitmap.h" +# include "sysprof-document-loader.h" +# include "sysprof-document-log.h" +# include "sysprof-document-mark.h" +# include "sysprof-document-metadata.h" +# include "sysprof-document-mmap.h" +# include "sysprof-document-overlay.h" +# include "sysprof-document-process.h" +# include "sysprof-document-sample.h" +# include "sysprof-document-traceable.h" +# include "sysprof-document.h" +# include "sysprof-elf-symbolizer.h" +# include "sysprof-energy-usage.h" +# include "sysprof-enums.h" +# include "sysprof-instrument.h" +# include "sysprof-jitmap-symbolizer.h" +# include "sysprof-kallsyms-symbolizer.h" +# include "sysprof-malloc-tracing.h" +# include "sysprof-mark-catalog.h" +# include "sysprof-memory-usage.h" +# include "sysprof-mount.h" +# include "sysprof-multi-symbolizer.h" +# include "sysprof-network-usage.h" +# include "sysprof-no-symbolizer.h" +# include "sysprof-power-profile.h" # include "sysprof-profiler.h" -# include "sysprof-proxy-source.h" -# include "sysprof-selection.h" -# include "sysprof-source.h" +# include "sysprof-proxied-instrument.h" +# include "sysprof-recording.h" +# include "sysprof-sampler.h" # include "sysprof-spawnable.h" -# include "sysprof-symbol-resolver.h" -# include "sysprof-symbols-source.h" -# include "sysprof-tracefd-source.h" - -#ifdef __linux__ -# include "sysprof-hostinfo-source.h" -# include "sysprof-memory-source.h" -# include "sysprof-perf-source.h" -# include "sysprof-proc-source.h" -#endif - +# include "sysprof-symbol.h" +# include "sysprof-symbolizer.h" +# include "sysprof-symbols-bundle.h" +# include "sysprof-system-logs.h" +# include "sysprof-thread-info.h" +# include "sysprof-time-span.h" +# include "sysprof-tracer.h" #undef SYSPROF_INSIDE G_END_DECLS diff --git a/src/libsysprof/tests/meson.build b/src/libsysprof/tests/meson.build new file mode 100644 index 00000000..8f5b20ca --- /dev/null +++ b/src/libsysprof/tests/meson.build @@ -0,0 +1,46 @@ +libsysprof_test_env = [ + 'G_DEBUG=gc-friendly', + 'GSETTINGS_BACKEND=memory', + 'MALLOC_CHECK_=2', +] + +libsysprof_testsuite_c_args = [ + '-DG_ENABLE_DEBUG', + '-UG_DISABLE_ASSERT', + '-UG_DISABLE_CAST_CHECKS', +] + +libsysprof_testsuite = { + 'read-build-id' : {'skip': true}, + 'test-callgraph' : {'skip': true}, + 'test-capture-model' : {'skip': true}, + 'test-elf-loader' : {'skip': true}, + 'test-list-counters' : {'skip': true}, + 'test-list-cpu' : {'skip': true}, + 'test-list-files' : {'skip': true}, + 'test-list-jitmap' : {'skip': true}, + 'test-list-overlays' : {'skip': true}, + 'test-maps-parser' : {'skip': true}, + 'test-mark-catalog' : {'skip': true}, + 'test-print-file' : {'skip': true}, + 'test-profiler' : {'skip': true}, + 'test-list-processes' : {'skip': true}, + 'test-list-address-layout' : {'skip': true}, + 'test-symbolize' : {'skip': true}, + 'test-strings' : {}, + 'test-symbol-cache' : {}, +} + +libsysprof_testsuite_deps = [ + libsysprof_static_dep, +] + +foreach test, params: libsysprof_testsuite + test_exe = executable(test, '@0@.c'.format(test), + c_args: libsysprof_testsuite_c_args, + dependencies: libsysprof_testsuite_deps, + ) + if not params.get('skip', false) + test(test, test_exe, env: libsysprof_test_env) + endif +endforeach diff --git a/src/tests/read-build-id.c b/src/libsysprof/tests/read-build-id.c similarity index 100% rename from src/tests/read-build-id.c rename to src/libsysprof/tests/read-build-id.c diff --git a/src/libsysprof/tests/test-callgraph.c b/src/libsysprof/tests/test-callgraph.c new file mode 100644 index 00000000..7a10d694 --- /dev/null +++ b/src/libsysprof/tests/test-callgraph.c @@ -0,0 +1,228 @@ +/* test-callgraph.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include + +typedef struct _Augment +{ + guint64 toplevel : 1; + guint64 size : 31; + guint32 total : 32; +} Augment; + +static char *kallsyms_path; +static gboolean include_threads; +static GEnumClass *category_class; +static gboolean summary_only; +static const GOptionEntry entries[] = { + { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms_path, "The path to kallsyms to use for decoding", "PATH" }, + { "threads", 't', 0, G_OPTION_ARG_NONE, &include_threads, "Include threads in the stack traces" }, + { "summary", 's', 0, G_OPTION_ARG_NONE, &summary_only, "Only show summary" }, + { 0 } +}; + +static void +print_callgraph (GListModel *model, + guint depth, + guint total) +{ + guint n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofCallgraphFrame) frame = g_list_model_get_item (model, i); + SysprofSymbol *symbol = sysprof_callgraph_frame_get_symbol (frame); + Augment *aug = sysprof_callgraph_frame_get_augment (frame); + SysprofCallgraphCategory category = sysprof_callgraph_frame_get_category (frame); + const char*category_name = g_enum_get_value (category_class, category)->value_nick; + char tstr[16]; + + g_snprintf (tstr, sizeof tstr, "%.2lf%%", 100. * (aug->total / (double)total)); + + g_print (" [%6u] [%8s] ", aug->total, tstr); + for (guint j = 0; j < depth; j++) + g_print (" "); + g_print ("%s [%s]\n", sysprof_symbol_get_name (symbol), category_name); + + print_callgraph (G_LIST_MODEL (frame), depth+1, total); + } +} + +static void +summarize_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofCallgraphFrame *frame = SYSPROF_CALLGRAPH_FRAME (object); + GMainLoop *main_loop = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(GListModel) model = NULL; + guint n_items; + + model = sysprof_callgraph_frame_summarize_finish (frame, result, &error); + g_assert_no_error (error); + g_assert_nonnull (model); + g_assert_true (G_IS_LIST_MODEL (model)); + + g_print ("\n\nSummary\n"); + + n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofCategorySummary) summary = g_list_model_get_item (model, i); + + g_assert_nonnull (summary); + g_assert_true (SYSPROF_IS_CATEGORY_SUMMARY (summary)); + + g_print ("%s: %lf\n", + g_enum_to_string (SYSPROF_TYPE_CALLGRAPH_CATEGORY, + sysprof_category_summary_get_category (summary)), + sysprof_category_summary_get_fraction (summary)); + } + + g_main_loop_quit (main_loop); +} + +static void +callgraph_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocument *document = SYSPROF_DOCUMENT (object); + GMainLoop *main_loop = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(SysprofCallgraph) callgraph = sysprof_document_callgraph_finish (document, result, &error); + g_autoptr(SysprofCallgraphFrame) root = NULL; + Augment *aug; + + g_assert_no_error (error); + g_assert_true (SYSPROF_IS_CALLGRAPH (callgraph)); + + root = g_list_model_get_item (G_LIST_MODEL (callgraph), 0); + aug = sysprof_callgraph_frame_get_augment (root); + + if (!summary_only) + { + g_print (" Hits Percent\n"); + print_callgraph (G_LIST_MODEL (callgraph), 0, aug->total); + } + + sysprof_callgraph_frame_summarize_async (root, NULL, summarize_cb, main_loop); +} + +static void +augment_cb (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gboolean summarize, + gpointer user_data) +{ + Augment *aug; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + g_assert (node != NULL); + g_assert (SYSPROF_IS_DOCUMENT_SAMPLE (frame)); + g_assert (user_data == NULL); + + aug = sysprof_callgraph_get_augment (callgraph, node); + aug->toplevel = TRUE; + + for (; node ; node = sysprof_callgraph_node_parent (node)) + { + aug = sysprof_callgraph_get_augment (callgraph, node); + aug->total += 1; + } +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("- test callgraph generation"); + g_autoptr(GMainLoop) main_loop = g_main_loop_new (NULL, FALSE); + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(SysprofMultiSymbolizer) multi = NULL; + g_autoptr(GListModel) samples = NULL; + g_autoptr(GError) error = NULL; + SysprofCallgraphFlags flags = 0; + + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + g_option_context_add_main_entries (context, entries, NULL); + if (!g_option_context_parse (context, &argc, &argv, &error)) + g_error ("%s", error->message); + + if (argc < 2) + g_error ("usage: %s CAPTURE_FILE", argv[0]); + + category_class = g_type_class_ref (SYSPROF_TYPE_CALLGRAPH_CATEGORY); + + multi = sysprof_multi_symbolizer_new (); + + if (kallsyms_path) + { + g_autoptr(GFile) kallsyms_file = g_file_new_for_path (kallsyms_path); + GFileInputStream *kallsyms_stream = g_file_read (kallsyms_file, NULL, NULL); + + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new_for_symbols (G_INPUT_STREAM (kallsyms_stream))); + } + else + { + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); + } + + sysprof_multi_symbolizer_take (multi, sysprof_elf_symbolizer_new ()); + sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + g_error ("Failed to load document: %s", error->message); + + samples = sysprof_document_list_samples (document); + + if (include_threads) + flags |= SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS; + + flags |= SYSPROF_CALLGRAPH_FLAGS_CATEGORIZE_FRAMES; + + sysprof_document_callgraph_async (document, + flags, + samples, + sizeof (Augment), + augment_cb, NULL, NULL, + NULL, + callgraph_cb, + main_loop); + + g_main_loop_run (main_loop); + + return 0; +} diff --git a/src/libsysprof/tests/test-capture-model.c b/src/libsysprof/tests/test-capture-model.c new file mode 100644 index 00000000..c509c6d8 --- /dev/null +++ b/src/libsysprof/tests/test-capture-model.c @@ -0,0 +1,147 @@ +#include +#include + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GError) error = NULL; + const char *filename; + guint n_items; + + sysprof_clock_init (); + + if (argc < 2) + { + g_printerr ("usage: %s FILENAME\n", argv[0]); + return 1; + } + + filename = argv[1]; + + loader = sysprof_document_loader_new (filename); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to load %s: %s\n", + filename, error->message); + return 1; + } + + n_items = g_list_model_get_n_items (G_LIST_MODEL (document)); + + for (guint i = 0; i < n_items; i++) + { + SysprofDocumentFrame *frame = g_list_model_get_item (G_LIST_MODEL (document), i); + + g_print ("%"G_GINT64_FORMAT" [pid %d] [cpu %d] (type %s)", + sysprof_document_frame_get_time (frame), + sysprof_document_frame_get_pid (frame), + sysprof_document_frame_get_cpu (frame), + G_OBJECT_TYPE_NAME (frame)); + + if (0) {} + else if (SYSPROF_IS_DOCUMENT_LOG (frame)) + g_print (" domain=%s message=%s", + sysprof_document_log_get_domain (SYSPROF_DOCUMENT_LOG (frame)), + sysprof_document_log_get_message (SYSPROF_DOCUMENT_LOG (frame))); + else if (SYSPROF_IS_DOCUMENT_MARK (frame)) + g_print (" group=%s name=%s message=%s", + sysprof_document_mark_get_group (SYSPROF_DOCUMENT_MARK (frame)), + sysprof_document_mark_get_name (SYSPROF_DOCUMENT_MARK (frame)), + sysprof_document_mark_get_message (SYSPROF_DOCUMENT_MARK (frame))); + else if (SYSPROF_IS_DOCUMENT_PROCESS (frame)) + g_print (" cmdline=%s", sysprof_document_process_get_command_line (SYSPROF_DOCUMENT_PROCESS (frame))); + else if (SYSPROF_IS_DOCUMENT_METADATA (frame)) + g_print (" id=%s value=%s", + sysprof_document_metadata_get_id (SYSPROF_DOCUMENT_METADATA (frame)), + sysprof_document_metadata_get_value (SYSPROF_DOCUMENT_METADATA (frame))); + else if (SYSPROF_IS_DOCUMENT_FILE_CHUNK (frame)) + g_print (" file-chunk path=%s len=%u", + sysprof_document_file_chunk_get_path (SYSPROF_DOCUMENT_FILE_CHUNK (frame)), + sysprof_document_file_chunk_get_size (SYSPROF_DOCUMENT_FILE_CHUNK (frame))); + else if (SYSPROF_IS_DOCUMENT_FORK (frame)) + g_print (" child-pid=%d", + sysprof_document_fork_get_child_pid (SYSPROF_DOCUMENT_FORK (frame))); + else if (SYSPROF_IS_DOCUMENT_OVERLAY (frame)) + g_print (" layer=%u source=%s destination=%s", + sysprof_document_overlay_get_layer (SYSPROF_DOCUMENT_OVERLAY (frame)), + sysprof_document_overlay_get_source (SYSPROF_DOCUMENT_OVERLAY (frame)), + sysprof_document_overlay_get_destination (SYSPROF_DOCUMENT_OVERLAY (frame))); + else if (SYSPROF_IS_DOCUMENT_MMAP (frame)) + g_print (" begin=0x%"G_GINT64_MODIFIER"x end=0x%"G_GINT64_MODIFIER"x offset=0x%"G_GINT64_MODIFIER"x path=%s", + sysprof_document_mmap_get_start_address (SYSPROF_DOCUMENT_MMAP (frame)), + sysprof_document_mmap_get_end_address (SYSPROF_DOCUMENT_MMAP (frame)), + sysprof_document_mmap_get_file_offset (SYSPROF_DOCUMENT_MMAP (frame)), + sysprof_document_mmap_get_file (SYSPROF_DOCUMENT_MMAP (frame))); + else if (SYSPROF_IS_DOCUMENT_JITMAP (frame)) + g_print (" n_jitmaps=%u", + sysprof_document_jitmap_get_size (SYSPROF_DOCUMENT_JITMAP (frame))); + else if (SYSPROF_IS_DOCUMENT_CTRDEF (frame)) + { + guint n_counters = sysprof_document_ctrdef_get_n_counters (SYSPROF_DOCUMENT_CTRDEF (frame)); + + g_print (" n_counters=%u", n_counters); + + for (guint j = 0; j < n_counters; j++) + { + const char *category, *name; + guint id, type; + + sysprof_document_ctrdef_get_counter (SYSPROF_DOCUMENT_CTRDEF (frame), + j, &id, &type, &category, &name, NULL); + g_print (" [%u<%s>:%s.%s]", + id, + type == SYSPROF_CAPTURE_COUNTER_INT64 ? "i64" : "f64", + category, name); + } + } + else if (SYSPROF_IS_DOCUMENT_CTRSET (frame)) + { + guint n_values = sysprof_document_ctrset_get_n_values (SYSPROF_DOCUMENT_CTRSET (frame)); + + g_print (" counters=["); + for (guint j = 0; j < n_values; j++) + { + guint id; + guint8 raw[8]; + + sysprof_document_ctrset_get_raw_value (SYSPROF_DOCUMENT_CTRSET (frame), j, &id, raw); + + g_print ("%u", id); + if (j+1 != n_values) + g_print (", "); + } + g_print ("]"); + } + else if (SYSPROF_IS_DOCUMENT_ALLOCATION (frame)) + { + if (sysprof_document_allocation_is_free (SYSPROF_DOCUMENT_ALLOCATION (frame))) + g_print (" 0x%016"G_GINT64_MODIFIER"x: free", + sysprof_document_allocation_get_address (SYSPROF_DOCUMENT_ALLOCATION (frame))); + else + g_print (" 0x%016"G_GINT64_MODIFIER"x: allocate %"G_GUINT64_FORMAT, + sysprof_document_allocation_get_address (SYSPROF_DOCUMENT_ALLOCATION (frame)), + sysprof_document_allocation_get_size (SYSPROF_DOCUMENT_ALLOCATION (frame))); + } + + if (SYSPROF_IS_DOCUMENT_TRACEABLE (frame)) + g_print (" stack-depth=%u", + sysprof_document_traceable_get_stack_depth (SYSPROF_DOCUMENT_TRACEABLE (frame))); + + g_print ("\n"); + + g_clear_object (&frame); + } + + g_printerr ("%u frames\n", n_items); + + return 0; +} diff --git a/src/libsysprof/tests/test-elf-loader.c b/src/libsysprof/tests/test-elf-loader.c new file mode 100644 index 00000000..41874c22 --- /dev/null +++ b/src/libsysprof/tests/test-elf-loader.c @@ -0,0 +1,86 @@ +/* test-elf-loader.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-process-private.h" +#include "sysprof-elf-loader-private.h" + +static const GOptionEntry entries[] = { + { 0 } +}; + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("- test loading of ELF files"); + g_autoptr(GMainLoop) main_loop = g_main_loop_new (NULL, FALSE); + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofElfLoader) elf_loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) processes = NULL; + g_autoptr(GError) error = NULL; + guint n_processes; + + g_option_context_add_main_entries (context, entries, NULL); + if (!g_option_context_parse (context, &argc, &argv, &error)) + g_error ("%s", error->message); + + if (argc < 2) + g_error ("usage: %s CAPTURE_FILE", argv[0]); + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + g_error ("Failed to load document: %s", error->message); + + processes = sysprof_document_list_processes (document); + n_processes = g_list_model_get_n_items (processes); + + elf_loader = sysprof_elf_loader_new (); + + for (guint i = 0; i < n_processes; i++) + { + g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); + g_autoptr(GListModel) memory_maps = sysprof_document_process_list_memory_maps (process); + guint n_memory_maps = g_list_model_get_n_items (memory_maps); + SysprofProcessInfo *info = _sysprof_document_process_get_info (process); + + for (guint j = 0; j < n_memory_maps; j++) + { + g_autoptr(SysprofDocumentMmap) memory_map = g_list_model_get_item (memory_maps, j); + const char *file = sysprof_document_mmap_get_file (memory_map); + const char *build_id = sysprof_document_mmap_get_build_id (memory_map); + guint64 file_inode = sysprof_document_mmap_get_file_inode (memory_map); + g_autoptr(SysprofElf) elf = sysprof_elf_loader_load (elf_loader, info->mount_namespace, file, build_id, file_inode, &error); + + if (elf == NULL) + g_print ("%u: %s (build-id %s) (inode %"G_GINT64_FORMAT") => [unresolved]\n", + info->pid, file, build_id ? build_id : "none", file_inode); + else + g_print ("%u: %s => %s\n", info->pid, file, sysprof_elf_get_file (elf)); + + g_clear_error (&error); + } + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-address-layout.c b/src/libsysprof/tests/test-list-address-layout.c new file mode 100644 index 00000000..37b9fdbd --- /dev/null +++ b/src/libsysprof/tests/test-list-address-layout.c @@ -0,0 +1,80 @@ +/* test-list-address-layout.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) processes = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + int pid; + + if (argc < 3) + { + g_printerr ("usage: %s CAPTURE_FILE PID\n", argv[0]); + return 1; + } + + pid = atoi (argv[2]); + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + processes = sysprof_document_list_processes (document); + n_items = g_list_model_get_n_items (processes); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); + + if (sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (process)) == pid) + { + g_autoptr(GListModel) memory_maps = sysprof_document_process_list_memory_maps (process); + guint n_maps = g_list_model_get_n_items (memory_maps); + + for (guint j = 0; j < n_maps; j++) + { + g_autoptr(SysprofDocumentMmap) map = g_list_model_get_item (memory_maps, j); + + g_print (" [0x%"G_GINT64_MODIFIER"x:0x%"G_GINT64_MODIFIER"x] %s <+0x%"G_GINT64_MODIFIER"x>\n", + sysprof_document_mmap_get_start_address (map), + sysprof_document_mmap_get_end_address (map), + sysprof_document_mmap_get_file (map), + sysprof_document_mmap_get_file_offset (map)); + } + + break; + } + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-counters.c b/src/libsysprof/tests/test-list-counters.c new file mode 100644 index 00000000..579fd296 --- /dev/null +++ b/src/libsysprof/tests/test-list-counters.c @@ -0,0 +1,108 @@ +/* test-list-counters.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +static gboolean show_values; +static const GOptionEntry entries[] = { + { "values", 'v', 0, G_OPTION_ARG_NONE, &show_values, "Show values along with counter information" }, + { 0 } +}; + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("- list counter information from capture"); + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) model = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + model = sysprof_document_list_counters (document); + n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentCounter) counter = g_list_model_get_item (model, i); + + g_print ("%d<%s> %s.%s: %s (%u values)\n", + sysprof_document_counter_get_id (counter), + sysprof_document_counter_get_value_type (counter) == G_TYPE_INT64 ? "i64" : "f64", + sysprof_document_counter_get_category (counter), + sysprof_document_counter_get_name (counter), + sysprof_document_counter_get_description (counter), + sysprof_document_counter_get_n_values (counter)); + + if (show_values) + { + guint n_values = sysprof_document_counter_get_n_values (counter); + + if (sysprof_document_counter_get_value_type (counter) == G_TYPE_INT64) + { + for (guint j = 0; j < n_values; j++) + { + gint64 t; + gint64 v = sysprof_document_counter_get_value_int64 (counter, j, &t); + + g_print (" %03u: %"G_GINT64_FORMAT": %"G_GINT64_FORMAT"\n", j, t, v); + } + } + else if (sysprof_document_counter_get_value_type (counter) == G_TYPE_DOUBLE) + { + for (guint j = 0; j < n_values; j++) + { + gint64 t; + double v = sysprof_document_counter_get_value_double (counter, j, &t); + + g_print (" %03u: %"G_GINT64_FORMAT": %lf\n", j, t, v); + } + } + } + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-cpu.c b/src/libsysprof/tests/test-list-cpu.c new file mode 100644 index 00000000..d5da5255 --- /dev/null +++ b/src/libsysprof/tests/test-list-cpu.c @@ -0,0 +1,76 @@ +/* test-list-counters.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +static const GOptionEntry entries[] = { + { 0 } +}; + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("- list cpu information from capture"); + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) model = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + model = sysprof_document_list_cpu_info (document); + n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofCpuInfo) cpu_info = g_list_model_get_item (model, i); + + g_print ("processor %u: %s\n", + sysprof_cpu_info_get_id (cpu_info), + sysprof_cpu_info_get_model_name (cpu_info)); + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-files.c b/src/libsysprof/tests/test-list-files.c new file mode 100644 index 00000000..5559dec7 --- /dev/null +++ b/src/libsysprof/tests/test-list-files.c @@ -0,0 +1,61 @@ +/* test-list-files.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) files = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + files = sysprof_document_list_files (document); + n_items = g_list_model_get_n_items (files); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentFile) file = g_list_model_get_item (files, i); + + g_print ("%s\n", sysprof_document_file_get_path (file)); + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-jitmap.c b/src/libsysprof/tests/test-list-jitmap.c new file mode 100644 index 00000000..9ae637ef --- /dev/null +++ b/src/libsysprof/tests/test-list-jitmap.c @@ -0,0 +1,76 @@ +/* test-list-jitmap.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) model = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + model = sysprof_document_list_jitmaps (document); + n_items = g_list_model_get_n_items (model); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentFrame) frame = g_list_model_get_item (model, i); + + if (SYSPROF_IS_DOCUMENT_JITMAP (frame)) + { + SysprofDocumentJitmap *jitmap = SYSPROF_DOCUMENT_JITMAP (frame); + guint size = sysprof_document_jitmap_get_size (jitmap); + + for (guint j = 0; j < size; j++) + { + SysprofAddress address; + const char *name; + + if (!(name = sysprof_document_jitmap_get_mapping (jitmap, j, &address))) + break; + + g_print ("0x%"G_GINT64_MODIFIER"x: %s\n", address, name); + } + } + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-overlays.c b/src/libsysprof/tests/test-list-overlays.c new file mode 100644 index 00000000..43c24f10 --- /dev/null +++ b/src/libsysprof/tests/test-list-overlays.c @@ -0,0 +1,107 @@ +/* test-list-overlays.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) processes = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + int pid = -1; + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE [PID]\n", argv[0]); + return 1; + } + + if (argc == 3) + pid = atoi (argv[2]); + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + processes = sysprof_document_list_processes (document); + n_items = g_list_model_get_n_items (processes); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); + g_autoptr(GListModel) mounts = sysprof_document_process_list_mounts (process); + g_autoptr(GString) str = g_string_new (NULL); + guint n_mounts; + + if (pid != -1 && pid != sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (process))) + continue; + + n_mounts = g_list_model_get_n_items (mounts); + for (guint j = 0; j < n_mounts; j++) + { + g_autoptr(SysprofMount) mount = g_list_model_get_item (mounts, j); + const char *fs_type = sysprof_mount_get_filesystem_type (mount); + const char *value; + + if (g_strcmp0 (fs_type, "overlay") == 0) + g_string_append_printf (str, + " @ID(%d) @ROOT(%s) @MOUNT_POINT(%s) @MOUNT_SOURCE(%s) @FS_TYPE(%s) @OPTIONS(%s)\n", + sysprof_mount_get_mount_id (mount), + sysprof_mount_get_root (mount), + sysprof_mount_get_mount_point (mount), + sysprof_mount_get_mount_source (mount), + sysprof_mount_get_filesystem_type (mount), + sysprof_mount_get_superblock_options (mount)); + + if ((value = sysprof_mount_get_superblock_option (mount, "lowerdir"))) + { + g_auto(GStrv) split = g_strsplit (value, ":", 0); + + g_string_append_printf (str, " LOWER: %s\n", value); + for (guint k = 0; split[k]; k++) + g_string_append_printf (str, " [%d]: %s\n", k, split[k]); + } + + if ((value = sysprof_mount_get_superblock_option (mount, "upperdir"))) + g_string_append_printf (str, " UPPER: %s\n", value); + + if ((value = sysprof_mount_get_superblock_option (mount, "workdir"))) + g_string_append_printf (str, " WORKDIR: %s\n", value); + } + + if (str->len > 0) + g_print ("%d:\n%s", + sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (process)), + str->str); + } + + return 0; +} diff --git a/src/libsysprof/tests/test-list-processes.c b/src/libsysprof/tests/test-list-processes.c new file mode 100644 index 00000000..3e571ddb --- /dev/null +++ b/src/libsysprof/tests/test-list-processes.c @@ -0,0 +1,122 @@ +/* test-list-processes.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) processes = NULL; + g_autoptr(GError) error = NULL; + guint n_items; + int pid = -1; + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE [PID]\n", argv[0]); + return 1; + } + + if (argc == 3) + pid = atoi (argv[2]); + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + processes = sysprof_document_list_processes (document); + n_items = g_list_model_get_n_items (processes); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentProcess) process = g_list_model_get_item (processes, i); + g_autoptr(GListModel) memory_maps = sysprof_document_process_list_memory_maps (process); + g_autoptr(GListModel) mounts = sysprof_document_process_list_mounts (process); + g_autoptr(GListModel) threads = sysprof_document_process_list_threads (process); + guint n_maps; + guint n_mounts; + guint n_threads; + + if (pid != -1 && pid != sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (process))) + continue; + + g_print ("%d: %s (Duration %lf seconds)\n", + sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (process)), + sysprof_document_process_get_command_line (process), + sysprof_document_process_get_duration (process) / (double)SYSPROF_NSEC_PER_SEC); + + g_print (" Address Layout:\n"); + n_maps = g_list_model_get_n_items (memory_maps); + for (guint j = 0; j < n_maps; j++) + { + g_autoptr(SysprofDocumentMmap) map = g_list_model_get_item (memory_maps, j); + + g_print (" [0x%"G_GINT64_MODIFIER"x:0x%"G_GINT64_MODIFIER"x] %s\n", + sysprof_document_mmap_get_start_address (map), + sysprof_document_mmap_get_end_address (map), + sysprof_document_mmap_get_file (map)); + } + + g_print (" Mounts:\n"); + n_mounts = g_list_model_get_n_items (mounts); + for (guint j = 0; j < n_mounts; j++) + { + g_autoptr(SysprofMount) mount = g_list_model_get_item (mounts, j); + g_autofree char *subvol = sysprof_mount_get_superblock_option (mount, "subvol"); + + g_print (" %d %d %d:%d %s %s %s %s %s\n", + sysprof_mount_get_mount_id (mount), + sysprof_mount_get_parent_mount_id (mount), + sysprof_mount_get_device_major (mount), + sysprof_mount_get_device_minor (mount), + sysprof_mount_get_root (mount), + sysprof_mount_get_mount_point (mount), + sysprof_mount_get_mount_source (mount), + sysprof_mount_get_filesystem_type (mount), + sysprof_mount_get_superblock_options (mount)); + + if (subvol != NULL) + g_print (" Had subvol superblock option: %s\n", subvol); + } + + g_print (" Threads:\n"); + n_threads = g_list_model_get_n_items (threads); + for (guint j = 0; j < n_threads; j++) + { + g_autoptr(SysprofThreadInfo) thread = g_list_model_get_item (threads, j); + + g_print (" Thread-Id: %i%s\n", + sysprof_thread_info_get_thread_id (thread), + sysprof_thread_info_is_main_thread (thread) ? " [Main Thread]" : ""); + } + } + + return 0; +} diff --git a/src/libsysprof/tests/test-maps-parser.c b/src/libsysprof/tests/test-maps-parser.c new file mode 100644 index 00000000..6363bb85 --- /dev/null +++ b/src/libsysprof/tests/test-maps-parser.c @@ -0,0 +1,51 @@ +/* test-maps-parser.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-maps-parser-private.h" + +#define ADDR_FORMAT "0x%"G_GINT64_MODIFIER"x" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GError) error = NULL; + g_autofree char *contents = NULL; + SysprofMapsParser parser; + guint64 begin, end, offset, inode; + char *file; + gsize len; + + g_assert_cmpint (argc, >, 1); + g_file_get_contents (argv[1], &contents, &len, &error); + g_assert_no_error (error); + + sysprof_maps_parser_init (&parser, contents, len); + while (sysprof_maps_parser_next (&parser, &begin, &end, &offset, &inode, &file)) + { + g_print (ADDR_FORMAT" - "ADDR_FORMAT" "ADDR_FORMAT" %"G_GUINT64_FORMAT" %s\n", + begin, end, offset, inode, file); + g_free (file); + } + + return 0; +} diff --git a/src/libsysprof/tests/test-mark-catalog.c b/src/libsysprof/tests/test-mark-catalog.c new file mode 100644 index 00000000..3452d5a5 --- /dev/null +++ b/src/libsysprof/tests/test-mark-catalog.c @@ -0,0 +1,72 @@ +/* test-mark-catalog.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) groups = NULL; + g_autoptr(GError) error = NULL; + guint n_groups; + + if (argc < 2) + { + g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + groups = sysprof_document_catalog_marks (document); + n_groups = g_list_model_get_n_items (groups); + + for (guint i = 0; i < n_groups; i++) + { + g_autoptr(GListModel) catalogs = g_list_model_get_item (groups, i); + guint n_catalogs = g_list_model_get_n_items (catalogs); + + for (guint j = 0; j < n_catalogs; j++) + { + g_autoptr(SysprofMarkCatalog) catalog = g_list_model_get_item (catalogs, j); + const char *group = sysprof_mark_catalog_get_group (catalog); + const char *name = sysprof_mark_catalog_get_name (catalog); + + if (j == 0) + g_print ("%s\n", group); + + g_print (" %s\n", name); + } + } + + return 0; +} diff --git a/src/libsysprof/tests/test-print-file.c b/src/libsysprof/tests/test-print-file.c new file mode 100644 index 00000000..ae0c193b --- /dev/null +++ b/src/libsysprof/tests/test-print-file.c @@ -0,0 +1,66 @@ +/* test-print-file.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include "sysprof-document-private.h" + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) files = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GBytes) bytes = NULL; + + if (argc < 3) + { + g_printerr ("usage: %s CAPTURE_FILE EMBEDDED_FILE_PATH\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + sysprof_document_loader_set_symbolizer (loader, sysprof_no_symbolizer_get ()); + + if (!(document = sysprof_document_loader_load (loader, NULL, &error))) + { + g_printerr ("Failed to open capture: %s\n", error->message); + return 1; + } + + file = sysprof_document_lookup_file (document, argv[2]); + + if (file == NULL) + { + g_printerr ("File \"%s\" not found.\n", argv[2]); + return 1; + } + + bytes = sysprof_document_file_dup_bytes (file); + + write (STDOUT_FILENO, + g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes)); + + return 0; +} diff --git a/src/libsysprof/tests/test-profiler.c b/src/libsysprof/tests/test-profiler.c new file mode 100644 index 00000000..a7a6d5e6 --- /dev/null +++ b/src/libsysprof/tests/test-profiler.c @@ -0,0 +1,235 @@ +/* test-profiler.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include + +static GMainLoop *main_loop; +static char *capture_file; +static SysprofRecording *active_recording; +static gboolean memprof; +static gboolean tracer; +static gboolean gnome_shell; +static char *power_profile; +static const GOptionEntry entries[] = { + { "capture", 'c', 0, G_OPTION_ARG_FILENAME, &capture_file, "The file to capture into", "CAPTURE" }, + { "memprof", 'm', 0, G_OPTION_ARG_NONE, &memprof, "Do memory allocation tracking on subprocess" }, + { "tracer", 't', 0, G_OPTION_ARG_NONE, &tracer, "Enable tracing with __cyg_profile_enter" }, + { "gnome-shell", 's', 0, G_OPTION_ARG_NONE, &gnome_shell, "Request GNOME Shell to provide profiler data" }, + { "power-profile", 'p', 0, G_OPTION_ARG_STRING, &power_profile, "Use POWER_PROFILE for duration of recording", "power-saver|balanced|performance" }, + { 0 } +}; + +static void +wait_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofRecording *recording = (SysprofRecording *)object; + g_autoptr(GError) error = NULL; + gboolean r; + + r = sysprof_recording_wait_finish (recording, result, &error); + g_assert_no_error (error); + g_assert_true (r); + + g_main_loop_quit (main_loop); +} + +static void +diagnostics_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + gpointer user_data) +{ + for (guint i = 0; i < added; i++) + { + g_autoptr(SysprofDiagnostic) diagnostic = g_list_model_get_item (model, position+i); + + g_printerr ("%s: %s\n", + sysprof_diagnostic_get_domain (diagnostic), + sysprof_diagnostic_get_message (diagnostic)); + } +} + +static void +record_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GError) error = NULL; + g_autoptr(SysprofRecording) recording = sysprof_profiler_record_finish (SYSPROF_PROFILER (object), result, &error); + g_autoptr(GListModel) diagnostics = NULL; + + g_assert_no_error (error); + g_assert_nonnull (recording); + g_assert_true (SYSPROF_IS_RECORDING (recording)); + + diagnostics = sysprof_recording_list_diagnostics (recording); + g_signal_connect (diagnostics, + "items-changed", + G_CALLBACK (diagnostics_items_changed_cb), + NULL); + diagnostics_items_changed_cb (diagnostics, + 0, + 0, + g_list_model_get_n_items (diagnostics), + NULL); + + sysprof_recording_wait_async (recording, NULL, wait_cb, NULL); + + active_recording = recording; +} + +static gboolean +sigint_handler (gpointer user_data) +{ + static int count; + + if (count >= 2) + { + g_main_loop_quit (main_loop); + return G_SOURCE_REMOVE; + } + + g_printerr ("\n"); + + if (count == 0) + { + g_printerr ("%s\n", "Stopping profiler. Press twice more ^C to force exit."); + sysprof_recording_stop_async (active_recording, NULL, NULL, NULL); + } + + count++; + + return G_SOURCE_CONTINUE; +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("[-- [COMMAND...]]"); + g_autoptr(SysprofProfiler) profiler = NULL; + g_autoptr(GError) error = NULL; + g_auto(GStrv) argv_copy = NULL; + SysprofCaptureWriter *writer = NULL; + SysprofCaptureReader *reader = NULL; + g_autofd int trace_fd = -1; + int argv_copy_len = 0; + + sysprof_clock_init (); + + main_loop = g_main_loop_new (NULL, FALSE); + + argv_copy = g_new0 (char *, argc+1); + for (guint i = 0; i < argc; i++) + { + if (strcmp ("--", argv[i]) == 0) + { + argv_copy[i] = NULL; + break; + } + + argv_copy[i] = g_strdup (argv[i]); + argv_copy_len++; + } + + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argv_copy_len, &argv_copy, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + + if (capture_file == NULL) + writer = sysprof_capture_writer_new ("capture.syscap", 0); + else + writer = sysprof_capture_writer_new (capture_file, 0); + + profiler = sysprof_profiler_new (); + + sysprof_profiler_add_instrument (profiler, sysprof_battery_charge_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_cpu_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_energy_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); + + if (power_profile) + sysprof_profiler_add_instrument (profiler, sysprof_power_profile_new (power_profile)); + + if (gnome_shell) + sysprof_profiler_add_instrument (profiler, + sysprof_proxied_instrument_new (G_BUS_TYPE_SESSION, + "org.gnome.Shell", + "/org/gnome/Sysprof3/Profiler")); + if (memprof) + sysprof_profiler_add_instrument (profiler, sysprof_malloc_tracing_new ()); + + if (tracer) + sysprof_profiler_add_instrument (profiler, sysprof_tracer_new ()); + else + sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); + + for (int i = 1; i < argc; i++) + { + if (strcmp (argv[i], "--") == 0 && i+1 < argc) + { + g_autoptr(SysprofSpawnable) spawnable = sysprof_spawnable_new (); + + sysprof_spawnable_append_args (spawnable, (const char * const *)&argv[i+1]); + sysprof_spawnable_set_cwd (spawnable, g_get_current_dir ()); + + sysprof_profiler_set_spawnable (profiler, spawnable); + + trace_fd = sysprof_spawnable_add_trace_fd (spawnable, NULL); + + break; + } + } + + sysprof_profiler_record_async (profiler, writer, NULL, record_cb, NULL); + + g_unix_signal_add (SIGINT, sigint_handler, main_loop); + g_unix_signal_add (SIGTERM, sigint_handler, main_loop); + + g_main_loop_run (main_loop); + + if (trace_fd != -1) + { + if ((reader = sysprof_capture_reader_new_from_fd (g_steal_fd (&trace_fd)))) + sysprof_capture_writer_cat (writer, reader); + } + + sysprof_capture_writer_flush (writer); + + g_clear_pointer (&reader, sysprof_capture_reader_unref); + g_clear_pointer (&writer, sysprof_capture_writer_unref); + + return 0; +} diff --git a/src/libsysprof-ui/sysprof-failed-state-view.h b/src/libsysprof/tests/test-strings.c similarity index 52% rename from src/libsysprof-ui/sysprof-failed-state-view.h rename to src/libsysprof/tests/test-strings.c index 4657abe1..dacf7ed7 100644 --- a/src/libsysprof-ui/sysprof-failed-state-view.h +++ b/src/libsysprof/tests/test-strings.c @@ -1,6 +1,6 @@ -/* sysprof-failed-state-view.h +/* test-strings.c * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,24 +18,31 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#pragma once +#include "config.h" -#include #include -G_BEGIN_DECLS +#include "sysprof-strings-private.h" -#define SYSPROF_TYPE_FAILED_STATE_VIEW (sysprof_failed_state_view_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofFailedStateView, sysprof_failed_state_view, SYSPROF, FAILED_STATE_VIEW, GtkWidget) - -struct _SysprofFailedStateViewClass +static void +test_basic (void) { - GtkWidgetClass parent; -}; + SysprofStrings *strings = sysprof_strings_new (); -GtkWidget *sysprof_failed_state_view_new (void); -void sysprof_failed_state_view_set_profiler (SysprofFailedStateView *self, - SysprofProfiler *profiler); + g_ref_string_release (sysprof_strings_get (strings, "123")); + g_ref_string_release (sysprof_strings_get (strings, "456")); -G_END_DECLS + g_ref_string_release (sysprof_strings_get (strings, "123")); + g_ref_string_release (sysprof_strings_get (strings, "456")); + + sysprof_strings_unref (strings); +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/libsysprof/Strings/basic", test_basic); + return g_test_run (); +} diff --git a/src/libsysprof/tests/test-symbol-cache.c b/src/libsysprof/tests/test-symbol-cache.c new file mode 100644 index 00000000..61c0c4a5 --- /dev/null +++ b/src/libsysprof/tests/test-symbol-cache.c @@ -0,0 +1,244 @@ +/* test-symbol-cache.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#include + +#include "sysprof-symbol-private.h" +#include "sysprof-symbol-cache-private.h" + +typedef struct _SymbolInfo +{ + const char *name; + guint64 begin; + guint64 end; + int position; + int sort; + SysprofSymbol *symbol; +} SymbolInfo; + +static SysprofSymbol * +create_symbol (const char *name, + guint64 begin, + guint64 end) +{ + g_assert (begin < end); + + return _sysprof_symbol_new (g_ref_string_new (name), NULL, NULL, begin, end, SYSPROF_SYMBOL_KIND_USER); +} + +static int +sort_by_key (gconstpointer a, + gconstpointer b) +{ + const SymbolInfo *info_a = a; + const SymbolInfo *info_b = b; + + if (info_a->sort < info_b->sort) + return -1; + else if (info_a->sort > info_b->sort) + return 1; + else + return 0; +} + +static int +sort_by_position (gconstpointer a, + gconstpointer b) +{ + const SymbolInfo *info_a = a; + const SymbolInfo *info_b = b; + + if (info_a->position < info_b->position) + return -1; + else if (info_a->position > info_b->position) + return 1; + else + return 0; +} + +static void +test_interval_tree (void) +{ + SysprofSymbolCache *symbol_cache = sysprof_symbol_cache_new (); + SymbolInfo symbols[] = { + { "symbol1", 0x10000, 0x20000 }, + { "symbol2", 0x20000, 0x30000 }, + { "symbol3", 0x30000, 0x40000 }, + { "symbol4", 0x90000, 0xa0000 }, + { "symbol5", 0xb0000, 0xb0001 }, + { "symbol6", 0xb0001, 0xb0002 }, + }; + + /* Add some randomness on insertion */ + for (guint i = 0; i < G_N_ELEMENTS (symbols); i++) + { + symbols[i].position = i; + symbols[i].sort = g_random_int (); + } + + /* Sort randomly for insertion */ + qsort (symbols, G_N_ELEMENTS (symbols), sizeof (SymbolInfo), sort_by_key); + for (guint i = 0; i < G_N_ELEMENTS (symbols); i++) + { + SymbolInfo *info = &symbols[i]; + + g_assert_cmpint (info->begin, <, info->end); + + info->symbol = create_symbol (info->name, info->begin, info->end); + + g_assert_nonnull (info->symbol); + g_assert_true (SYSPROF_IS_SYMBOL (info->symbol)); + + sysprof_symbol_cache_take (symbol_cache, g_object_ref (info->symbol)); + } + + /* Now resort to do lookups with edge checking */ + qsort (symbols, G_N_ELEMENTS (symbols), sizeof (SymbolInfo), sort_by_position); + for (guint i = 0; i < G_N_ELEMENTS (symbols); i++) + { + const SymbolInfo *info = &symbols[i]; + const SymbolInfo *prev = i > 0 ? &symbols[i-1] : NULL; + const SymbolInfo *next = i + 1 < G_N_ELEMENTS (symbols) ? &symbols[i+1] : NULL; + SysprofSymbol *lookup; + + g_assert_cmpint (info->position, ==, i); + + lookup = sysprof_symbol_cache_lookup (symbol_cache, info->begin-1); + if (prev && info->begin == prev->end) + g_assert_true (lookup == prev->symbol); + else + g_assert_null (lookup); + + lookup = sysprof_symbol_cache_lookup (symbol_cache, info->begin); + g_assert_nonnull (lookup); + g_assert_true (lookup == info->symbol); + + lookup = sysprof_symbol_cache_lookup (symbol_cache, info->end); + if (next == NULL || next->begin > info->end) + g_assert_null (lookup); + else + g_assert_true (lookup == next->symbol); + + if (info->begin+1 != info->end) + { + lookup = sysprof_symbol_cache_lookup (symbol_cache, info->begin+1); + g_assert_nonnull (lookup); + g_assert_true (lookup == info->symbol); + } + + lookup = sysprof_symbol_cache_lookup (symbol_cache, info->end-1); + g_assert_nonnull (lookup); + g_assert_true (lookup == info->symbol); + + lookup = sysprof_symbol_cache_lookup (symbol_cache, info->begin + ((info->end-info->begin)/2)); + g_assert_nonnull (lookup); + g_assert_true (lookup == info->symbol); + } + + g_assert_finalize_object (symbol_cache); + + for (guint i = 0; i < G_N_ELEMENTS (symbols); i++) + g_assert_finalize_object (symbols[i].symbol); +} + +static void +test_jitmap (void) +{ + SysprofSymbolCache *symbol_cache = sysprof_symbol_cache_new (); + + for (guint i = 1; i <= 10000; i++) + { + SysprofAddress begin = 0xE000000000000000 + i; + g_autofree char *name = g_strdup_printf ("%u", i); + SysprofSymbol *symbol = create_symbol (name, begin, begin+1); + + sysprof_symbol_cache_take (symbol_cache, symbol); + } + + g_assert_null (sysprof_symbol_cache_lookup (symbol_cache, 0)); + g_assert_null (sysprof_symbol_cache_lookup (symbol_cache, 10001)); + + for (guint i = 1; i <= 10000; i++) + { + SysprofAddress begin = 0xE000000000000000 + i; + SysprofSymbol *symbol = sysprof_symbol_cache_lookup (symbol_cache, begin); + g_autofree char *name = g_strdup_printf ("%u", i); + + g_assert_nonnull (symbol); + g_assert_cmpint (begin, ==, symbol->begin_address); + g_assert_cmpint (begin+1, ==, symbol->end_address); + g_assert_cmpstr (name, ==, symbol->name); + } + + g_assert_finalize_object (symbol_cache); +} + +static void +test_collision (void) +{ + SysprofSymbolCache *symbol_cache = sysprof_symbol_cache_new (); + SysprofSymbol *first = NULL; + + for (guint i = 1; i <= 10000; i++) + { + SysprofAddress begin = 0xe000000000000000 + i; + g_autofree char *name = g_strdup_printf ("%u", i); + SysprofSymbol *symbol = create_symbol (name, begin, begin+1); + + if (first == NULL) + first = g_object_ref (symbol); + + sysprof_symbol_cache_take (symbol_cache, symbol); + } + + g_assert_true (SYSPROF_IS_SYMBOL (first)); + + for (guint i = 1; i <= 10000; i++) + { + SysprofAddress begin = 0xE000000000000000 + i; + g_autofree char *name = g_strdup_printf ("%u", i); + SysprofSymbol *symbol = create_symbol (name, begin, begin+1); + + sysprof_symbol_cache_take (symbol_cache, symbol); + } + + g_assert_finalize_object (symbol_cache); + g_assert_finalize_object (first); + + /* To test this fully, you need `-Db_sanitize=address` so that + * you can detect any leaks from RB_INSERT(). + */ +} + +int +main (int argc, + char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/libsysprof/SysprofSymbolCache/interval-tree", + test_interval_tree); + g_test_add_func ("/libsysprof/SysprofSymbolCache/jitmap", + test_jitmap); + g_test_add_func ("/libsysprof/SysprofSymbolCache/collision", + test_collision); + return g_test_run (); +} diff --git a/src/libsysprof/tests/test-symbolize.c b/src/libsysprof/tests/test-symbolize.c new file mode 100644 index 00000000..d3fdd2c2 --- /dev/null +++ b/src/libsysprof/tests/test-symbolize.c @@ -0,0 +1,173 @@ +#include "config.h" + +#include + +#include "sysprof-document-private.h" + +static GMainLoop *main_loop; +static gboolean silent; +static gboolean no_bundled; +static char **debug_dirs; +static char *kallsyms_path; +static const GOptionEntry entries[] = { + { "no-bundled", 'b', 0, G_OPTION_ARG_NONE, &no_bundled, "Ignore symbols bundled in capture file" }, + { "silent", 's', 0, G_OPTION_ARG_NONE, &silent, "Do not print symbol information" }, + { "debug-dir", 'd', 0, G_OPTION_ARG_FILENAME_ARRAY, &debug_dirs, "Specify external debug directory, may be repeated" }, + { "kallsyms", 'k', 0, G_OPTION_ARG_FILENAME, &kallsyms_path, "Specify path to kallsyms for kernel symbolizing" }, + { 0 } +}; + + +static void +load_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocumentLoader *loader = (SysprofDocumentLoader *)object; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GListModel) traceables = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GString) str = NULL; + SysprofSymbol *symbols[128]; + guint n_symbols; + guint n_items; + + g_assert (SYSPROF_IS_DOCUMENT_LOADER (loader)); + g_assert (G_IS_ASYNC_RESULT (result)); + + if (!(document = sysprof_document_loader_load_finish (loader, result, &error))) + g_error ("Failed to load document: %s", error->message); + + traceables = sysprof_document_list_traceables (document); + n_items = g_list_model_get_n_items (traceables); + + if (silent) + { + g_main_loop_quit (main_loop); + return; + } + + str = g_string_new (""); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentTraceable) traceable = g_list_model_get_item (traceables, i); + + str->len = 0; + str->str[0] = 0; + + g_assert (traceable != NULL); + g_assert (SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + + n_symbols = sysprof_document_symbolize_traceable (document, + traceable, + symbols, + G_N_ELEMENTS (symbols), + NULL); + + g_print ("%s depth=%u pid=%u\n", + G_OBJECT_TYPE_NAME (traceable), + n_symbols, + sysprof_document_frame_get_pid (SYSPROF_DOCUMENT_FRAME (traceable))); + + for (guint j = 0; j < n_symbols; j++) + { + SysprofSymbol *symbol = symbols[j]; + const char *name; + const char *path; + const char *nick; + + if (symbol != NULL) + { + name = sysprof_symbol_get_name (symbol); + path = sysprof_symbol_get_binary_path (symbol); + nick = sysprof_symbol_get_binary_nick (symbol); + } + else + { + name = path = nick = NULL; + } + + g_string_append_printf (str, + " %02d: 0x%"G_GINT64_MODIFIER"x:", + j, + sysprof_document_traceable_get_stack_address (traceable, j)); + + if (name) + g_string_append_printf (str, " %s", name); + + if (path) + g_string_append_printf (str, " [%s]", path); + + if (nick) + g_string_append_printf (str, " (%s)", nick); + + g_string_append_c (str, '\n'); + } + + g_string_append (str, " ================\n"); + + write (STDOUT_FILENO, str->str, str->len); + } + + g_print ("Document symbolized\n"); + + g_main_loop_quit (main_loop); +} + +int +main (int argc, + char *argv[]) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GError) error = NULL; + + main_loop = g_main_loop_new (NULL, FALSE); + context = g_option_context_new ("- test document symbolization"); + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr ("%s\n", error->message); + return 1; + } + + if (argc != 2 || !g_file_test (argv[1], G_FILE_TEST_EXISTS)) + { + g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); + return 1; + } + + loader = sysprof_document_loader_new (argv[1]); + + if (TRUE) + { + g_autoptr(SysprofMultiSymbolizer) multi = sysprof_multi_symbolizer_new (); + SysprofSymbolizer *elf = sysprof_elf_symbolizer_new (); + g_autoptr(GFile) kallsyms_file = kallsyms_path ? g_file_new_for_path (kallsyms_path) : NULL; + GFileInputStream *kallsyms_stream = kallsyms_file ? g_file_read (kallsyms_file, NULL, NULL) : NULL; + + if (debug_dirs) + sysprof_elf_symbolizer_set_external_debug_dirs (SYSPROF_ELF_SYMBOLIZER (elf), + (const char * const *)debug_dirs); + + if (!no_bundled) + sysprof_multi_symbolizer_take (multi, sysprof_bundled_symbolizer_new ()); + + if (kallsyms_stream == NULL) + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new ()); + else + sysprof_multi_symbolizer_take (multi, sysprof_kallsyms_symbolizer_new_for_symbols (G_INPUT_STREAM (kallsyms_stream))); + + sysprof_multi_symbolizer_take (multi, elf); + sysprof_multi_symbolizer_take (multi, sysprof_jitmap_symbolizer_new ()); + + sysprof_document_loader_set_symbolizer (loader, SYSPROF_SYMBOLIZER (multi)); + } + + sysprof_document_loader_load_async (loader, NULL, load_cb, NULL); + g_main_loop_run (main_loop); + + return 0; +} diff --git a/src/libsysprof/tree.h b/src/libsysprof/tree.h new file mode 100644 index 00000000..d61f1313 --- /dev/null +++ b/src/libsysprof/tree.h @@ -0,0 +1,1004 @@ +/* $OpenBSD: tree.h,v 1.31 2023/03/08 04:43:09 guenther Exp $ */ +/* + * Copyright 2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _SYS_TREE_H_ +#define _SYS_TREE_H_ + +#include + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __unused __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __unused __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __unused __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root))) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ +attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ +attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ +attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ +attr struct type *name##_RB_INSERT(struct name *, struct type *); \ +attr struct type *name##_RB_FIND(struct name *, struct type *); \ +attr struct type *name##_RB_NFIND(struct name *, struct type *); \ +attr struct type *name##_RB_NEXT(struct type *); \ +attr struct type *name##_RB_PREV(struct type *); \ +attr struct type *name##_RB_MINMAX(struct name *, int); \ + \ + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} \ + \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)))\ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)))\ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} \ + \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field))) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field))); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} \ + \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} \ + \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} \ + \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), 1); \ + (x) = (y)) + + +/* + * Copyright (c) 2016 David Gwynne + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct rb_type { + int (*t_compare)(const void *, const void *); + void (*t_augment)(void *); + unsigned int t_offset; /* offset of rb_entry in type */ +}; + +struct rb_tree { + struct rb_entry *rbt_root; +}; + +struct rb_entry { + struct rb_entry *rbt_parent; + struct rb_entry *rbt_left; + struct rb_entry *rbt_right; + unsigned int rbt_color; +}; + +#define RBT_HEAD(_name, _type) \ +struct _name { \ + struct rb_tree rbh_root; \ +} + +#define RBT_ENTRY(_type) struct rb_entry + +static inline void +_rb_init(struct rb_tree *rbt) +{ + rbt->rbt_root = NULL; +} + +static inline int +_rb_empty(struct rb_tree *rbt) +{ + return (rbt->rbt_root == NULL); +} + +void *_rb_insert(const struct rb_type *, struct rb_tree *, void *); +void *_rb_remove(const struct rb_type *, struct rb_tree *, void *); +void *_rb_find(const struct rb_type *, struct rb_tree *, const void *); +void *_rb_nfind(const struct rb_type *, struct rb_tree *, const void *); +void *_rb_root(const struct rb_type *, struct rb_tree *); +void *_rb_min(const struct rb_type *, struct rb_tree *); +void *_rb_max(const struct rb_type *, struct rb_tree *); +void *_rb_next(const struct rb_type *, void *); +void *_rb_prev(const struct rb_type *, void *); +void *_rb_left(const struct rb_type *, void *); +void *_rb_right(const struct rb_type *, void *); +void *_rb_parent(const struct rb_type *, void *); +void _rb_set_left(const struct rb_type *, void *, void *); +void _rb_set_right(const struct rb_type *, void *, void *); +void _rb_set_parent(const struct rb_type *, void *, void *); +void _rb_poison(const struct rb_type *, void *, unsigned long); +int _rb_check(const struct rb_type *, void *, unsigned long); + +#define RBT_INITIALIZER(_head) { { NULL } } + +#define RBT_PROTOTYPE(_name, _type, _field, _cmp) \ +extern const struct rb_type *const _name##_RBT_TYPE; \ + \ +__unused static inline void \ +_name##_RBT_INIT(struct _name *head) \ +{ \ + _rb_init(&head->rbh_root); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_INSERT(struct _name *head, struct _type *elm) \ +{ \ + return _rb_insert(_name##_RBT_TYPE, &head->rbh_root, elm); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_REMOVE(struct _name *head, struct _type *elm) \ +{ \ + return _rb_remove(_name##_RBT_TYPE, &head->rbh_root, elm); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_FIND(struct _name *head, const struct _type *key) \ +{ \ + return _rb_find(_name##_RBT_TYPE, &head->rbh_root, key); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_NFIND(struct _name *head, const struct _type *key) \ +{ \ + return _rb_nfind(_name##_RBT_TYPE, &head->rbh_root, key); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_ROOT(struct _name *head) \ +{ \ + return _rb_root(_name##_RBT_TYPE, &head->rbh_root); \ +} \ + \ +__unused static inline int \ +_name##_RBT_EMPTY(struct _name *head) \ +{ \ + return _rb_empty(&head->rbh_root); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_MIN(struct _name *head) \ +{ \ + return _rb_min(_name##_RBT_TYPE, &head->rbh_root); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_MAX(struct _name *head) \ +{ \ + return _rb_max(_name##_RBT_TYPE, &head->rbh_root); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_NEXT(struct _type *elm) \ +{ \ + return _rb_next(_name##_RBT_TYPE, elm); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_PREV(struct _type *elm) \ +{ \ + return _rb_prev(_name##_RBT_TYPE, elm); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_LEFT(struct _type *elm) \ +{ \ + return _rb_left(_name##_RBT_TYPE, elm); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_RIGHT(struct _type *elm) \ +{ \ + return _rb_right(_name##_RBT_TYPE, elm); \ +} \ + \ +__unused static inline struct _type * \ +_name##_RBT_PARENT(struct _type *elm) \ +{ \ + return _rb_parent(_name##_RBT_TYPE, elm); \ +} \ + \ +__unused static inline void \ +_name##_RBT_SET_LEFT(struct _type *elm, struct _type *left) \ +{ \ + _rb_set_left(_name##_RBT_TYPE, elm, left); \ +} \ + \ +__unused static inline void \ +_name##_RBT_SET_RIGHT(struct _type *elm, struct _type *right) \ +{ \ + _rb_set_right(_name##_RBT_TYPE, elm, right); \ +} \ + \ +__unused static inline void \ +_name##_RBT_SET_PARENT(struct _type *elm, struct _type *parent) \ +{ \ + _rb_set_parent(_name##_RBT_TYPE, elm, parent); \ +} \ + \ +__unused static inline void \ +_name##_RBT_POISON(struct _type *elm, unsigned long poison) \ +{ \ + _rb_poison(_name##_RBT_TYPE, elm, poison); \ +} \ + \ +__unused static inline int \ +_name##_RBT_CHECK(struct _type *elm, unsigned long poison) \ +{ \ + return _rb_check(_name##_RBT_TYPE, elm, poison); \ +} + +#define RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _aug) \ +static int \ +_name##_RBT_COMPARE(const void *lptr, const void *rptr) \ +{ \ + const struct _type *l = lptr, *r = rptr; \ + return _cmp(l, r); \ +} \ +static const struct rb_type _name##_RBT_INFO = { \ + _name##_RBT_COMPARE, \ + _aug, \ + offsetof(struct _type, _field), \ +}; \ +const struct rb_type *const _name##_RBT_TYPE = &_name##_RBT_INFO + +#define RBT_GENERATE_AUGMENT(_name, _type, _field, _cmp, _aug) \ +static void \ +_name##_RBT_AUGMENT(void *ptr) \ +{ \ + struct _type *p = ptr; \ + return _aug(p); \ +} \ +RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, _name##_RBT_AUGMENT) + +#define RBT_GENERATE(_name, _type, _field, _cmp) \ + RBT_GENERATE_INTERNAL(_name, _type, _field, _cmp, NULL) + +#define RBT_INIT(_name, _head) _name##_RBT_INIT(_head) +#define RBT_INSERT(_name, _head, _elm) _name##_RBT_INSERT(_head, _elm) +#define RBT_REMOVE(_name, _head, _elm) _name##_RBT_REMOVE(_head, _elm) +#define RBT_FIND(_name, _head, _key) _name##_RBT_FIND(_head, _key) +#define RBT_NFIND(_name, _head, _key) _name##_RBT_NFIND(_head, _key) +#define RBT_ROOT(_name, _head) _name##_RBT_ROOT(_head) +#define RBT_EMPTY(_name, _head) _name##_RBT_EMPTY(_head) +#define RBT_MIN(_name, _head) _name##_RBT_MIN(_head) +#define RBT_MAX(_name, _head) _name##_RBT_MAX(_head) +#define RBT_NEXT(_name, _elm) _name##_RBT_NEXT(_elm) +#define RBT_PREV(_name, _elm) _name##_RBT_PREV(_elm) +#define RBT_LEFT(_name, _elm) _name##_RBT_LEFT(_elm) +#define RBT_RIGHT(_name, _elm) _name##_RBT_RIGHT(_elm) +#define RBT_PARENT(_name, _elm) _name##_RBT_PARENT(_elm) +#define RBT_SET_LEFT(_name, _elm, _l) _name##_RBT_SET_LEFT(_elm, _l) +#define RBT_SET_RIGHT(_name, _elm, _r) _name##_RBT_SET_RIGHT(_elm, _r) +#define RBT_SET_PARENT(_name, _elm, _p) _name##_RBT_SET_PARENT(_elm, _p) +#define RBT_POISON(_name, _elm, _p) _name##_RBT_POISON(_elm, _p) +#define RBT_CHECK(_name, _elm, _p) _name##_RBT_CHECK(_elm, _p) + +#define RBT_FOREACH(_e, _name, _head) \ + for ((_e) = RBT_MIN(_name, (_head)); \ + (_e) != NULL; \ + (_e) = RBT_NEXT(_name, (_e))) + +#define RBT_FOREACH_SAFE(_e, _name, _head, _n) \ + for ((_e) = RBT_MIN(_name, (_head)); \ + (_e) != NULL && ((_n) = RBT_NEXT(_name, (_e)), 1); \ + (_e) = (_n)) + +#define RBT_FOREACH_REVERSE(_e, _name, _head) \ + for ((_e) = RBT_MAX(_name, (_head)); \ + (_e) != NULL; \ + (_e) = RBT_PREV(_name, (_e))) + +#define RBT_FOREACH_REVERSE_SAFE(_e, _name, _head, _n) \ + for ((_e) = RBT_MAX(_name, (_head)); \ + (_e) != NULL && ((_n) = RBT_PREV(_name, (_e)), 1); \ + (_e) = (_n)) + +#endif /* _SYS_TREE_H_ */ diff --git a/src/meson.build b/src/meson.build index 9d8817eb..c5afe9d4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,5 @@ sysprof_header_subdir = 'sysprof-@0@'.format(libsysprof_api_version) -sysprof_ui_header_subdir = 'sysprof-ui-@0@'.format(libsysprof_ui_api_version) +sysprof_header_dir = join_paths(get_option('includedir'), sysprof_header_subdir) sysprof_version_conf = configuration_data() sysprof_version = meson.project_version().split('.') @@ -11,65 +11,22 @@ sysprof_version_conf.set('MINOR_VERSION', sysprof_version[1]) sysprof_version_conf.set('MICRO_VERSION', sysprof_version[2]) sysprof_version_conf.set('VERSION', meson.project_version()) -if needs_service_access - ipc_profiler_src = gnome.gdbus_codegen('ipc-profiler', - sources: 'org.gnome.Sysprof3.Profiler.xml', - interface_prefix: 'org.gnome.Sysprof3.', - namespace: 'Ipc', - ) - - ipc_service_src = gnome.gdbus_codegen('ipc-service', - sources: 'org.gnome.Sysprof3.Service.xml', - interface_prefix: 'org.gnome.Sysprof3.', - namespace: 'Ipc', - ) - - ipc_legacy_src = gnome.gdbus_codegen('ipc-legacy', - sources: 'org.gnome.Sysprof2.xml', - interface_prefix: 'org.gnome.', - namespace: 'IpcLegacy', - ) - - ipc_agent_src = gnome.gdbus_codegen('ipc-agent', - sources: 'org.gnome.Sysprof.Agent.xml', - interface_prefix: 'org.gnome.Sysprof.', - namespace: 'Ipc', - ) -endif - -if install_service_files - install_data([ - 'org.gnome.Sysprof2.xml', - 'org.gnome.Sysprof3.Profiler.xml', - 'org.gnome.Sysprof3.Service.xml', - 'org.gnome.Sysprof.Agent.xml' - ], - install_dir: join_paths(datadir, 'dbus-1/interfaces'), - ) -endif - -ipc_include_dirs = include_directories('.') - -stackstash_sources = files('stackstash.c') - -helpers_sources = files('helpers.c') - subdir('libsysprof-capture') + +if need_libsysprof + subdir('libsysprof') + subdir('preload') +endif + if get_option('sysprofd') == 'bundled' subdir('sysprofd') endif -if get_option('libsysprof') or get_option('agent') - subdir('libsysprof') -endif -if get_option('gtk') and get_option('libsysprof') - subdir('libsysprof-ui') -endif -if get_option('gtk') and get_option('libsysprof') + +if get_option('gtk') subdir('sysprof') endif + if get_option('tools') - subdir('tools') -endif -if get_option('tests') - subdir('tests') + subdir('sysprof-agent') + subdir('sysprof-cli') endif diff --git a/src/org.gnome.Sysprof2.xml b/src/org.gnome.Sysprof2.xml deleted file mode 100644 index ede0326f..00000000 --- a/src/org.gnome.Sysprof2.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/libsysprof/preload/backtrace-helper.h b/src/preload/backtrace-helper.h similarity index 100% rename from src/libsysprof/preload/backtrace-helper.h rename to src/preload/backtrace-helper.h diff --git a/src/libsysprof/preload/gconstructor.h b/src/preload/gconstructor.h similarity index 100% rename from src/libsysprof/preload/gconstructor.h rename to src/preload/gconstructor.h diff --git a/src/libsysprof/preload/meson.build b/src/preload/meson.build similarity index 74% rename from src/libsysprof/preload/meson.build rename to src/preload/meson.build index a1a6e603..9c39af50 100644 --- a/src/libsysprof/preload/meson.build +++ b/src/preload/meson.build @@ -24,3 +24,10 @@ libsysprof_speedtrack_preload = shared_library('sysprof-speedtrack-@0@'.format(l install: true, install_dir: get_option('libdir'), ) + +libsysprof_tracer_preload = shared_library('sysprof-tracer-@0@'.format(libsysprof_api_version), + ['sysprof-tracer-collector.c'], + dependencies: preload_deps, + install: true, + install_dir: get_option('libdir'), +) diff --git a/src/libsysprof/preload/sysprof-memory-collector.c b/src/preload/sysprof-memory-collector.c similarity index 100% rename from src/libsysprof/preload/sysprof-memory-collector.c rename to src/preload/sysprof-memory-collector.c diff --git a/src/libsysprof/preload/sysprof-speedtrack-collector.c b/src/preload/sysprof-speedtrack-collector.c similarity index 100% rename from src/libsysprof/preload/sysprof-speedtrack-collector.c rename to src/preload/sysprof-speedtrack-collector.c diff --git a/src/libsysprof/preload/sysprof-tracer.c b/src/preload/sysprof-tracer-collector.c similarity index 71% rename from src/libsysprof/preload/sysprof-tracer.c rename to src/preload/sysprof-tracer-collector.c index 95937b0f..c427c5ad 100644 --- a/src/libsysprof/preload/sysprof-tracer.c +++ b/src/preload/sysprof-tracer-collector.c @@ -28,6 +28,15 @@ #include "gconstructor.h" +#ifdef __GNUC__ +# define GNUC_CHECK_VERSION(major, minor) \ + ((__GNUC__ > (major)) || \ + ((__GNUC__ == (major)) && \ + (__GNUC_MINOR__ >= (minor)))) +#else +# define GNUC_CHECK_VERSION(major, minor) 0 +#endif + #if defined (G_HAS_CONSTRUCTORS) # ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA # pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(collector_init_ctor) @@ -49,24 +58,30 @@ collector_init_ctor (void) sysprof_collector_init (); } -/* TODO: - * - * This is just an example. - * +/* * What we would really want to do is to have a new frame type for enter/exit * tracing so that we can only push/pop the new address to the sample. Then - * when decoding it can recreate stack traces if necessary. + * when decoding it can recreate stack traces if necessary. But for now, we + * can emulate that by just adding a sample when we enter a function and + * leave a function. The rest could be done in post-processing. */ +#if GNUC_CHECK_VERSION(3,0) +__attribute__((no_instrument_function)) +#endif void profile_func_enter (void *func, void *call_site) { - sysprof_collector_sample (backtrace_func, NULL); + sysprof_collector_trace (backtrace_func, NULL, TRUE); } +#if GNUC_CHECK_VERSION(3,0) +__attribute__((no_instrument_function)) +#endif void profile_func_exit (void *func, void *call_site) { + sysprof_collector_trace (backtrace_func, NULL, FALSE); } diff --git a/src/stackstash.c b/src/stackstash.c deleted file mode 100644 index 12256184..00000000 --- a/src/stackstash.c +++ /dev/null @@ -1,398 +0,0 @@ -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include "stackstash.h" - -struct StackStash -{ - int ref_count; - StackNode * root; - GHashTable * nodes_by_data; - GDestroyNotify destroy; - - StackNode * cached_nodes; - GPtrArray * blocks; -}; - -static void -decorate_node (StackNode *node, - StackStash *stash) -{ - StackNode *n; - - if (!node) - return; - - decorate_node (node->siblings, stash); - decorate_node (node->children, stash); - - node->next = g_hash_table_lookup (stash->nodes_by_data, &node->data); - g_hash_table_insert (stash->nodes_by_data, &node->data, node); - - /* FIXME: This could be done more efficiently - * by keeping track of the ancestors we have seen. - */ - node->toplevel = TRUE; - for (n = node->parent; n != NULL; n = n->parent) - { - if (n->data == node->data) - { - node->toplevel = FALSE; - break; - } - } -} - -static unsigned int -address_hash (gconstpointer key) -{ - const uint64_t *addr = key; - - return *addr; -} - -static gboolean -address_equal (gconstpointer key1, gconstpointer key2) -{ - const uint64_t *addr1 = key1; - const uint64_t *addr2 = key2; - - return *addr1 == *addr2; -} - -static void -stack_stash_decorate (StackStash *stash) -{ - if (stash->nodes_by_data) - return; - - stash->nodes_by_data = g_hash_table_new (address_hash, address_equal); - - decorate_node (stash->root, stash); -} - -static void -free_key (gpointer key, - gpointer value, - gpointer data) -{ - GDestroyNotify destroy = data; - uint64_t u64 = *(uint64_t *)key; - - destroy (U64_TO_POINTER (u64)); -} - -static void -stack_stash_undecorate (StackStash *stash) -{ - if (stash->nodes_by_data) - { - if (stash->destroy) - { - g_hash_table_foreach ( - stash->nodes_by_data, free_key, stash->destroy); - } - - g_hash_table_destroy (stash->nodes_by_data); - stash->nodes_by_data = NULL; - } -} - -static GHashTable * -get_nodes_by_data (StackStash *stash) -{ - if (!stash->nodes_by_data) - stack_stash_decorate (stash); - - return stash->nodes_by_data; -} - -StackNode * -stack_node_new (StackStash *stash) -{ - StackNode *node; - - if (!stash->cached_nodes) - { -#define BLOCK_SIZE 32768 -#define N_NODES (BLOCK_SIZE / sizeof (StackNode)) - - StackNode *block = g_malloc (BLOCK_SIZE); - guint i; - - for (i = 0; i < N_NODES; ++i) - { - block[i].next = stash->cached_nodes; - stash->cached_nodes = &(block[i]); - } - - g_ptr_array_add (stash->blocks, block); - } - - node = stash->cached_nodes; - stash->cached_nodes = node->next; - - node->siblings = NULL; - node->children = NULL; - node->data = 0; - node->parent = NULL; - node->size = 0; - node->next = NULL; - node->total = 0; - - return node; -} - -/* "destroy", if non-NULL, is called once on every address */ -static StackStash * -create_stack_stash (GDestroyNotify destroy) -{ - StackStash *stash = g_new (StackStash, 1); - - stash->root = NULL; - stash->nodes_by_data = NULL; - stash->ref_count = 1; - stash->destroy = destroy; - - stash->cached_nodes = NULL; - stash->blocks = g_ptr_array_new (); - - return stash; -} - -/* Stach */ -StackStash * -stack_stash_new (GDestroyNotify destroy) -{ - return create_stack_stash (destroy); -} - - -static void -stack_stash_free (StackStash *stash) -{ - guint i; - - stack_stash_undecorate (stash); - - for (i = 0; i < stash->blocks->len; ++i) - g_free (stash->blocks->pdata[i]); - - g_ptr_array_free (stash->blocks, TRUE); - - g_free (stash); -} - -StackNode * -stack_stash_add_trace (StackStash *stash, - const uint64_t *addrs, - int n_addrs, - int size) -{ - StackNode **location = &(stash->root); - StackNode *parent = NULL; - int i; - - if (!n_addrs) - return NULL; - - if (stash->nodes_by_data) - stack_stash_undecorate (stash); - - for (i = n_addrs - 1; i >= 0; --i) - { - StackNode *match = NULL; - StackNode *prev; - - /* FIXME: On x86-64 we don't get proper stacktraces which means - * each node can have tons of children. That makes this loop - * here show up on profiles. - * - * Not sure what can be done about it aside from actually fixing - * x86-64 to get stacktraces. - */ - prev = NULL; - for (match = *location; match; prev = match, match = match->siblings) - { - if (match->data == addrs[i]) - { - if (prev) - { - /* move to front */ - prev->siblings = match->siblings; - match->siblings = *location; - *location = match; - } - - break; - } - } - - if (!match) - { - match = stack_node_new (stash); - match->data = addrs[i]; - match->siblings = *location; - match->parent = parent; - *location = match; - } - - match->total += size; - - location = &(match->children); - parent = match; - } - - parent->size += size; - - return parent; -} - -static void -do_callback (StackNode *node, - StackLink *trace, - StackFunction func, - gpointer data) -{ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdangling-pointer" - - StackLink link; - - if (trace) - { - g_assert (trace->prev == NULL); - trace->prev = &link; - } - - link.next = trace; - link.prev = NULL; - - while (node) - { - link.data = node->data; - - if (node->size) - func (&link, node->size, data); - - do_callback (node->children, &link, func, data); - - node = node->siblings; - } - - if (trace) - { - g_assert (trace->prev == &link); - trace->prev = NULL; - } - -#pragma GCC diagnostic pop -} - -void -stack_stash_foreach (StackStash *stash, - StackFunction stack_func, - gpointer data) -{ - do_callback (stash->root, NULL, stack_func, data); -} - -void -stack_node_foreach_trace (StackNode *node, - StackFunction func, - gpointer data) -{ - StackLink link; - - link.next = NULL; - link.data = node->data; - link.prev = NULL; - - if (node->size) - func (&link, node->size, data); - - do_callback (node->children, &link, func, data); -} - -void -stack_stash_unref (StackStash *stash) -{ - stash->ref_count--; - if (stash->ref_count == 0) - stack_stash_free (stash); -} - -StackStash * -stack_stash_ref (StackStash *stash) -{ - stash->ref_count++; - return stash; -} - -StackNode * -stack_stash_find_node (StackStash *stash, - gpointer data) -{ - uint64_t u64 = POINTER_TO_U64 (data); - - g_return_val_if_fail (stash != NULL, NULL); - - return g_hash_table_lookup (get_nodes_by_data (stash), &u64); -} - -typedef struct -{ - StackNodeFunc func; - gpointer data; -} Info; - -static void -do_foreach (gpointer key, gpointer value, gpointer data) -{ - Info *info = data; - - info->func (value, info->data); -} - -void -stack_stash_foreach_by_address (StackStash *stash, - StackNodeFunc func, - gpointer data) -{ - Info info; - info.func = func; - info.data = data; - - g_hash_table_foreach (get_nodes_by_data (stash), do_foreach, &info); -} - -StackNode * -stack_stash_get_root (StackStash *stash) -{ - return stash->root; -} - -void -stack_stash_set_root (StackStash *stash, - StackNode *root) -{ - g_return_if_fail (stash->root == NULL); - - stash->root = root; -} diff --git a/src/stackstash.h b/src/stackstash.h deleted file mode 100644 index 9d2909bf..00000000 --- a/src/stackstash.h +++ /dev/null @@ -1,85 +0,0 @@ -/* Sysprof -- Sampling, systemwide CPU profiler - * Copyright 2004, Red Hat, Inc. - * Copyright 2004, 2005, Soeren Sandmann - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef STACK_STASH_H -#define STACK_STASH_H - -#include -#include - -typedef struct StackStash StackStash; -typedef struct StackNode StackNode; -typedef struct StackLink StackLink; - -#define U64_TO_POINTER(u) ((void *)(intptr_t)u) -#define POINTER_TO_U64(p) ((uint64_t)(intptr_t)p) - -struct StackNode -{ - uint64_t data; - - guint total : 32; - guint size : 31; - guint toplevel : 1; - - StackNode * parent; - StackNode * siblings; - StackNode * children; - - StackNode * next; -}; - -struct StackLink -{ - uint64_t data; - StackLink *next; - StackLink *prev; -}; - -typedef void (* StackFunction) (StackLink *trace, - gint size, - gpointer data); - -typedef void (* StackNodeFunc) (StackNode *node, - gpointer data); - -StackStash *stack_stash_new (GDestroyNotify destroy); -StackNode *stack_node_new (StackStash *stash); -StackNode *stack_stash_add_trace (StackStash *stash, - const uint64_t *addrs, - gint n_addrs, - int size); -void stack_stash_foreach (StackStash *stash, - StackFunction stack_func, - gpointer data); -void stack_node_foreach_trace (StackNode *node, - StackFunction stack_func, - gpointer data); -StackNode *stack_stash_find_node (StackStash *stash, - gpointer address); -void stack_stash_foreach_by_address (StackStash *stash, - StackNodeFunc func, - gpointer data); -StackNode *stack_stash_get_root (StackStash *stash); -StackStash *stack_stash_ref (StackStash *stash); -void stack_stash_unref (StackStash *stash); -void stack_stash_set_root (StackStash *stash, - StackNode *root); - -#endif diff --git a/src/sysprof-agent/meson.build b/src/sysprof-agent/meson.build new file mode 100644 index 00000000..7713abce --- /dev/null +++ b/src/sysprof-agent/meson.build @@ -0,0 +1,28 @@ +ipc_agent_src = gnome.gdbus_codegen('ipc-agent', + sources: 'org.gnome.Sysprof.Agent.xml', + interface_prefix: 'org.gnome.Sysprof.', + namespace: 'Ipc', +) + +sysprof_agent_sources = [ + 'sysprof-agent.c', + ipc_agent_src, +] + +sysprof_agent_c_args = [ +] + +sysprof_agent_deps = [ + libsysprof_static_dep, +] + +sysprof_agent = executable('sysprof-agent', sysprof_agent_sources, + dependencies: sysprof_agent_deps, + c_args: release_flags + sysprof_agent_c_args, + install_dir: get_option('bindir'), + install: true, +) + +install_data(['org.gnome.Sysprof.Agent.xml'], + install_dir: join_paths(datadir, 'dbus-1/interfaces'), +) diff --git a/src/org.gnome.Sysprof.Agent.xml b/src/sysprof-agent/org.gnome.Sysprof.Agent.xml similarity index 100% rename from src/org.gnome.Sysprof.Agent.xml rename to src/sysprof-agent/org.gnome.Sysprof.Agent.xml diff --git a/src/tools/sysprof-agent.c b/src/sysprof-agent/sysprof-agent.c similarity index 61% rename from src/tools/sysprof-agent.c rename to src/sysprof-agent/sysprof-agent.c index 0ba4d26b..458273e2 100644 --- a/src/tools/sysprof-agent.c +++ b/src/sysprof-agent/sysprof-agent.c @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -40,18 +42,16 @@ static gboolean forward_fd_func (const char *option_name, GError **error); static GMainLoop *main_loop; -static GSubprocess *subprocess; -static char *subprocess_ident; -static gboolean subprocess_finished; static IpcAgent *service; -static int exit_code = EXIT_SUCCESS; static int read_fd = -1; static int write_fd = -1; static int pty_fd = -1; static char *directory; static char *capture_filename; +static char *power_profile; static GArray *forward_fds; static char **env; +static SysprofRecording *active_recording; static gboolean clear_env; static gboolean aid_battery; static gboolean aid_compositor; @@ -85,8 +85,9 @@ static const GOptionEntry options[] = { { "energy", 0, 0, G_OPTION_ARG_NONE, &aid_energy, "Record energy usage using RAPL" }, { "battery", 0, 0, G_OPTION_ARG_NONE, &aid_battery, "Record battery charge and discharge rates" }, { "compositor", 0, 0, G_OPTION_ARG_NONE, &aid_compositor, "Record GNOME Shell compositor information" }, - { "no-throttle", 0, 0, G_OPTION_ARG_NONE, &no_throttle, "Disable CPU throttling" }, + { "no-throttle", 0, 0, G_OPTION_ARG_NONE, &no_throttle, "Disable CPU throttling [Deprecated for --power-profile]" }, { "tracefd", 0, 0, G_OPTION_ARG_NONE, &aid_tracefd, "Provide TRACEFD to subprocess" }, + { "power-profile", 0, 0, G_OPTION_ARG_STRING, &power_profile, "Use POWER_PROFILE for duration of recording", "power-saver|balanced|performance" }, { NULL } }; @@ -108,71 +109,6 @@ message (const char *format, ipc_agent_emit_log (service, formatted); } -#define GBP_TYPE_SPAWN_SOURCE (gbp_spawn_source_get_type()) -G_DECLARE_FINAL_TYPE (GbpSpawnSource, gbp_spawn_source, GBP, SPAWN_SOURCE, GObject) - -struct _GbpSpawnSource -{ - GObject parent_instance; -}; - -static void -gbp_spawn_source_modify_spawn (SysprofSource *source, - SysprofSpawnable *spawnable) -{ - g_assert (GBP_IS_SPAWN_SOURCE (source)); - g_assert (SYSPROF_IS_SPAWNABLE (spawnable)); - - if (forward_fds == NULL) - return; - - for (guint i = 0; i < forward_fds->len; i++) - { - int fd = g_array_index (forward_fds, int, i); - sysprof_spawnable_take_fd (spawnable, dup (fd), fd); - } - - if (pty_fd != -1) - { - sysprof_spawnable_take_fd (spawnable, dup (pty_fd), STDIN_FILENO); - sysprof_spawnable_take_fd (spawnable, dup (pty_fd), STDOUT_FILENO); - sysprof_spawnable_take_fd (spawnable, dup (pty_fd), STDERR_FILENO); - } -} - -static void -gbp_spawn_source_start (SysprofSource *source) -{ - sysprof_source_emit_ready (source); -} - -static void -gbp_spawn_source_stop (SysprofSource *source) -{ - sysprof_source_emit_finished (source); -} - -static void -spawn_source_init (SysprofSourceInterface *iface) -{ - iface->modify_spawn = gbp_spawn_source_modify_spawn; - iface->start = gbp_spawn_source_start; - iface->stop = gbp_spawn_source_stop; -} - -G_DEFINE_FINAL_TYPE_WITH_CODE (GbpSpawnSource, gbp_spawn_source, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (SYSPROF_TYPE_SOURCE, spawn_source_init)) - -static void -gbp_spawn_source_class_init (GbpSpawnSourceClass *klass) -{ -} - -static void -gbp_spawn_source_init (GbpSpawnSource *self) -{ -} - #define IPC_TYPE_AGENT_IMPL (ipc_agent_impl_get_type()) G_DECLARE_FINAL_TYPE (IpcAgentImpl, ipc_agent_impl, IPC, SYPSROF_IMPL, IpcAgentSkeleton) @@ -185,7 +121,10 @@ static gboolean handle_force_exit (IpcAgent *sysprof, GDBusMethodInvocation *invocation) { - if (subprocess && !subprocess_finished) + GSubprocess *subprocess; + + if (active_recording && + (subprocess = sysprof_recording_get_subprocess (active_recording))) g_subprocess_force_exit (subprocess); ipc_agent_complete_force_exit (sysprof, invocation); @@ -198,7 +137,10 @@ handle_send_signal (IpcAgent *sysprof, GDBusMethodInvocation *invocation, int signum) { - if (subprocess && !subprocess_finished) + GSubprocess *subprocess; + + if (active_recording && + (subprocess = sysprof_recording_get_subprocess (active_recording))) g_subprocess_send_signal (subprocess, signum); ipc_agent_complete_send_signal (sysprof, invocation); @@ -264,89 +206,6 @@ forward_fd_func (const char *option_name, return TRUE; } -G_GNUC_NULL_TERMINATED -static void -add_source (SysprofProfiler *profiler, - gboolean enabled, - GType source_type, - ...) -{ - g_autoptr(SysprofSource) source = NULL; - const char *first_property; - va_list args; - - g_assert (SYSPROF_IS_PROFILER (profiler)); - g_assert (g_type_is_a (source_type, SYSPROF_TYPE_SOURCE)); - - if (!enabled) - return; - - va_start (args, source_type); - first_property = va_arg (args, const char *); - if (first_property != NULL) - source = (SysprofSource *)g_object_new_valist (source_type, first_property, args); - else - source = g_object_new (source_type, NULL); - va_end (args); - - g_assert (!source || SYSPROF_IS_SOURCE (source)); - - if (source != NULL) - sysprof_profiler_add_source (profiler, source); - else - g_printerr ("Failed to create source of type \"%s\"\n", - g_type_name (source_type)); -} - -static void -profiler_failed_cb (SysprofProfiler *profiler, - const GError *error) -{ - g_assert (SYSPROF_IS_LOCAL_PROFILER (profiler)); - g_assert (error != NULL); - - g_printerr ("Profiling failed: %s", error->message); - exit_code = EXIT_FAILURE; - g_main_loop_quit (main_loop); -} - -static void -profiler_stopped_cb (SysprofProfiler *profiler) -{ - g_assert (SYSPROF_IS_LOCAL_PROFILER (profiler)); - - g_main_loop_quit (main_loop); -} - -static void -subprocess_spawned_cb (SysprofLocalProfiler *profiler, - GSubprocess *new_subprocess) -{ - g_assert (SYSPROF_IS_LOCAL_PROFILER (profiler)); - g_assert (G_IS_SUBPROCESS (new_subprocess)); - - g_set_object (&subprocess, new_subprocess); - - subprocess_ident = g_strdup (g_subprocess_get_identifier (subprocess)); - - message ("Created process %s", subprocess_ident); -} - -static void -subprocess_finished_cb (SysprofLocalProfiler *profiler, - GSubprocess *new_subprocess) -{ - g_assert (SYSPROF_IS_LOCAL_PROFILER (profiler)); - g_assert (G_IS_SUBPROCESS (new_subprocess)); - - subprocess_finished = TRUE; - - message ("Process %s exited", subprocess_ident); - - if (decode) - message ("Extracting symbols and attaching to capture"); -} - static void split_argv (int argc, char **argv, @@ -417,25 +276,109 @@ create_connection (GIOStream *stream, return ret; } +static void +sysprof_agent_recording_diagnostics_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + gpointer user_data) +{ + g_assert (G_IS_LIST_MODEL (model)); + g_assert (user_data == NULL); + + for (guint i = 0; i < added; i++) + { + g_autoptr(SysprofDiagnostic) diagnostic = g_list_model_get_item (model, position+i); + + message ("%s: %s", + sysprof_diagnostic_get_domain (diagnostic), + sysprof_diagnostic_get_message (diagnostic)); + } +} + static gboolean sigint_handler (gpointer user_data) { - SysprofProfiler *profiler = user_data; + static int count; + + if (count >= 2) + { + g_main_loop_quit (main_loop); + return G_SOURCE_REMOVE; + } + + g_printerr ("\n"); + + if (count == 0) + { + g_printerr ("%s\n", "Stopping profiler. Press twice more ^C to force exit."); + sysprof_recording_stop_async (active_recording, NULL, NULL, NULL); + } + + count++; + + return G_SOURCE_CONTINUE; +} + +static void +sysprof_agent_wait_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofRecording *recording = (SysprofRecording *)object; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (user_data == NULL); + + if (!sysprof_recording_wait_finish (recording, result, &error)) + g_error ("Failed to complete recording: %s", error->message); + + g_main_loop_quit (main_loop); +} + +static void +sysprof_agent_record_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofProfiler *profiler = (SysprofProfiler *)object; + g_autoptr(SysprofRecording) recording = NULL; + g_autoptr(GListModel) diagnostics = NULL; + g_autoptr(GError) error = NULL; g_assert (SYSPROF_IS_PROFILER (profiler)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (user_data == NULL); - g_printerr ("\n" - "Profiler stopped, extracting symbols and appending to capture.\n" - "Press ^C again to force exit.\n"); - sysprof_profiler_stop (profiler); - return G_SOURCE_REMOVE; + if (!(recording = sysprof_profiler_record_finish (SYSPROF_PROFILER (object), result, &error))) + g_error ("Failed to start profiling session: %s", error->message); + + diagnostics = sysprof_recording_list_diagnostics (recording); + g_signal_connect (diagnostics, + "items-changed", + G_CALLBACK (sysprof_agent_recording_diagnostics_items_changed_cb), + NULL); + sysprof_agent_recording_diagnostics_items_changed_cb (diagnostics, + 0, + 0, + g_list_model_get_n_items (diagnostics), + NULL); + + sysprof_recording_wait_async (recording, + NULL, + sysprof_agent_wait_cb, + NULL); + + g_set_object (&active_recording, recording); } int main (int argc, char *argv[]) { - g_autoptr(SysprofCaptureWriter) writer = NULL; + SysprofCaptureWriter *writer = NULL; g_autoptr(SysprofProfiler) profiler = NULL; g_autoptr(GDBusConnection) connection = NULL; g_autoptr(GDBusConnection) session_bus = NULL; @@ -444,7 +387,9 @@ main (int argc, g_autoptr(GError) error = NULL; g_auto(GStrv) our_argv = NULL; g_auto(GStrv) sub_argv = NULL; - GMainContext *main_context; + g_autofd int gjs_trace_fd = -1; + g_autofd int trace_fd = -1; + GSubprocess *subprocess; int our_argc = -1; int sub_argc = -1; @@ -538,14 +483,44 @@ main (int argc, } /* Now start setting up our profiler */ - profiler = sysprof_local_profiler_new (); + profiler = sysprof_profiler_new (); - /* We might not even know our real subprocess in the case we are going - * through another indirection layer like flatpak-spawn, so just assume - * we're profiling the entire system as that will be necessary to include - * the PID we really care about. - */ - sysprof_profiler_set_whole_system (profiler, TRUE); + if (aid_battery) + sysprof_profiler_add_instrument (profiler, sysprof_battery_charge_new ()); + + if (aid_cpu) + sysprof_profiler_add_instrument (profiler, sysprof_cpu_usage_new ()); + + if (aid_disk) + sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); + + if (aid_energy) + sysprof_profiler_add_instrument (profiler, sysprof_energy_usage_new ()); + + if (aid_memory) + sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); + + if (aid_net) + sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); + + if (aid_perf) + sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); + + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); + + if (power_profile) + sysprof_profiler_add_instrument (profiler, sysprof_power_profile_new (power_profile)); + else if (no_throttle) + sysprof_profiler_add_instrument (profiler, sysprof_power_profile_new ("performance")); + + if (aid_compositor) + sysprof_profiler_add_instrument (profiler, + sysprof_proxied_instrument_new (G_BUS_TYPE_SESSION, + "org.gnome.Shell", + "/org/gnome/Sysprof3/Profiler")); + + if (decode) + sysprof_profiler_add_instrument (profiler, sysprof_symbols_bundle_new ()); /* If -- was ommitted or there are no commands, just profile the entire * system without spawning anything. Really only useful when testing the @@ -553,15 +528,65 @@ main (int argc, */ if (sub_argc >= 0) { - sysprof_profiler_set_spawn (profiler, TRUE); - sysprof_profiler_set_spawn_inherit_environ (profiler, !clear_env); - sysprof_profiler_set_spawn_argv (profiler, (const char * const *)sub_argv); - sysprof_profiler_set_spawn_env (profiler, (const char * const *)env); + g_autoptr(SysprofSpawnable) spawnable = sysprof_spawnable_new (); + g_auto(GStrv) current_env = g_get_environ (); if (directory != NULL) - sysprof_profiler_set_spawn_cwd (profiler, directory); + sysprof_spawnable_set_cwd (spawnable, directory); else - sysprof_profiler_set_spawn_cwd (profiler, "."); + sysprof_spawnable_set_cwd (spawnable, g_get_current_dir ()); + + sysprof_spawnable_append_args (spawnable, (const char * const *)sub_argv); + + if (clear_env) + sysprof_spawnable_set_environ (spawnable, NULL); + else + sysprof_spawnable_set_environ (spawnable, (const char * const *)current_env); + + if (env != NULL) + { + for (guint i = 0; env[i]; i++) + { + g_autofree char *key = NULL; + const char *eq; + + if (!(eq = strchr (env[i], '='))) + { + sysprof_spawnable_setenv (spawnable, env[i], ""); + continue; + } + + key = g_strndup (env[i], eq - env[i]); + sysprof_spawnable_setenv (spawnable, key, eq+1); + } + } + + sysprof_profiler_set_spawnable (profiler, spawnable); + + if (aid_gjs) + gjs_trace_fd = sysprof_spawnable_add_trace_fd (spawnable, "GJS_TRACE_FD"); + + if (aid_tracefd) + trace_fd = sysprof_spawnable_add_trace_fd (spawnable, NULL); + + if (aid_memprof) + sysprof_spawnable_add_ld_preload (spawnable, PACKAGE_LIBDIR"/libsysprof-memory-"API_VERSION_S".so"); + + if (forward_fds != NULL) + { + for (guint f = 0; f < forward_fds->len; f++) + { + int fd = g_array_index (forward_fds, int, f); + sysprof_spawnable_take_fd (spawnable, dup (fd), fd); + } + } + + if (pty_fd != -1) + { + sysprof_spawnable_take_fd (spawnable, dup (pty_fd), STDIN_FILENO); + sysprof_spawnable_take_fd (spawnable, dup (pty_fd), STDOUT_FILENO); + sysprof_spawnable_take_fd (spawnable, dup (pty_fd), STDERR_FILENO); + } } /* Now open the writer for our session */ @@ -573,66 +598,14 @@ main (int argc, return EXIT_FAILURE; } - /* Attach writer to the profiler */ - sysprof_profiler_set_writer (profiler, writer); + sysprof_profiler_record_async (profiler, + writer, + NULL, + sysprof_agent_record_cb, + NULL); - /* Add all request sources */ - add_source (profiler, TRUE, GBP_TYPE_SPAWN_SOURCE, NULL); - add_source (profiler, TRUE, SYSPROF_TYPE_PROC_SOURCE, NULL); - add_source (profiler, TRUE, SYSPROF_TYPE_SYMBOLS_SOURCE, NULL); - add_source (profiler, aid_battery, SYSPROF_TYPE_BATTERY_SOURCE, NULL); - add_source (profiler, aid_compositor, SYSPROF_TYPE_PROXY_SOURCE, - "bus-type", G_BUS_TYPE_SESSION, - "bus-name", "org.gnome.Shell", - "object-path", "/org/gnome/Sysprof3/Profiler", - NULL); - add_source (profiler, aid_cpu, SYSPROF_TYPE_HOSTINFO_SOURCE, NULL); - add_source (profiler, aid_disk, SYSPROF_TYPE_DISKSTAT_SOURCE, NULL); - add_source (profiler, aid_energy, SYSPROF_TYPE_PROXY_SOURCE, - "bus-type", G_BUS_TYPE_SYSTEM, - "bus-name", "org.gnome.Sysprof3", - "object-path", "/org/gnome/Sysprof3/RAPL", - NULL); - add_source (profiler, aid_gjs, SYSPROF_TYPE_GJS_SOURCE, NULL); - add_source (profiler, aid_memory, SYSPROF_TYPE_MEMORY_SOURCE, NULL); - add_source (profiler, aid_memprof, SYSPROF_TYPE_MEMPROF_SOURCE, NULL); - add_source (profiler, aid_net, SYSPROF_TYPE_NETDEV_SOURCE, NULL); - add_source (profiler, aid_perf, SYSPROF_TYPE_PERF_SOURCE, NULL); - add_source (profiler, aid_tracefd, SYSPROF_TYPE_TRACEFD_SOURCE, - "envvar", "SYSPROF_TRACE_FD", - NULL); - add_source (profiler, no_throttle, SYSPROF_TYPE_GOVERNOR_SOURCE, - "disable-governor", TRUE, - NULL); - add_source (profiler, decode, SYSPROF_TYPE_SYMBOLS_SOURCE, NULL); - - /* Bail when we've failed or finished and track the subprocess - * so that we can deliver signals to it. - */ - g_signal_connect (profiler, - "failed", - G_CALLBACK (profiler_failed_cb), - NULL); - g_signal_connect (profiler, - "stopped", - G_CALLBACK (profiler_stopped_cb), - NULL); - g_signal_connect (profiler, - "subprocess-spawned", - G_CALLBACK (subprocess_spawned_cb), - NULL); - g_signal_connect (profiler, - "subprocess-finished", - G_CALLBACK (subprocess_finished_cb), - NULL); - - /* SIGINT (keyboard ^C) should stop the profiler and append symbols - * to the SysprofCaptureWriter. - */ - g_unix_signal_add (SIGINT, sigint_handler, profiler); - - /* Start the profiler */ - sysprof_profiler_start (profiler); + g_unix_signal_add (SIGINT, sigint_handler, main_loop); + g_unix_signal_add (SIGTERM, sigint_handler, main_loop); /* Now tell the connection to start processing messages that are * delivered from the controller, or signals destined back. @@ -640,29 +613,40 @@ main (int argc, if (connection != NULL) g_dbus_connection_start_message_processing (connection); - /* Wait for profiler to finish */ g_main_loop_run (main_loop); - /* Notify that some more work needs to proceed */ - message ("Completing symbol extraction"); + if (gjs_trace_fd != -1) + { + SysprofCaptureReader *reader = NULL; - /* Let anything in-flight finish */ - main_context = g_main_loop_get_context (main_loop); - while (g_main_context_pending (main_context)) - g_main_context_iteration (main_context, FALSE); + if ((reader = sysprof_capture_reader_new_from_fd (g_steal_fd (&gjs_trace_fd)))) + { + sysprof_capture_writer_cat (writer, reader); + sysprof_capture_reader_unref (reader); + } + } + + if (trace_fd != -1) + { + SysprofCaptureReader *reader = NULL; + + if ((reader = sysprof_capture_reader_new_from_fd (g_steal_fd (&trace_fd)))) + { + sysprof_capture_writer_cat (writer, reader); + sysprof_capture_reader_unref (reader); + } + } - /* Now make sure our bits are on disk */ sysprof_capture_writer_flush (writer); + sysprof_capture_writer_unref (writer); + + subprocess = sysprof_recording_get_subprocess (active_recording); /* Try to exit the same way as the subprocess did to propagate that * back into Builder who is watching *this* process. */ - if (subprocess_finished) + if (subprocess != NULL) { - g_assert (G_IS_SUBPROCESS (subprocess)); - g_assert (g_subprocess_get_if_exited (subprocess) || - g_subprocess_get_if_signaled (subprocess)); - if (g_subprocess_get_if_signaled (subprocess)) { int signum = g_subprocess_get_term_sig (subprocess); @@ -674,8 +658,9 @@ main (int argc, return EXIT_FAILURE; } - exit_code = g_subprocess_get_exit_status (subprocess); + if (g_subprocess_get_if_exited (subprocess)) + return g_subprocess_get_exit_status (subprocess); } - return exit_code; + return EXIT_SUCCESS; } diff --git a/src/sysprof-cli/meson.build b/src/sysprof-cli/meson.build new file mode 100644 index 00000000..4bf0acc6 --- /dev/null +++ b/src/sysprof-cli/meson.build @@ -0,0 +1,19 @@ +sysprof_cli_sources = [ + 'sysprof-cli.c', +] + +sysprof_cli_c_args = [ +] + +sysprof_cli_deps = [ + dependency('polkit-agent-1'), + + libsysprof_static_dep, +] + +sysprof_cli = executable('sysprof-cli', sysprof_cli_sources, + dependencies: sysprof_cli_deps, + c_args: release_flags + sysprof_cli_c_args, + install_dir: get_option('bindir'), + install: true, +) diff --git a/src/tools/sysprof-cli.c b/src/sysprof-cli/sysprof-cli.c similarity index 61% rename from src/tools/sysprof-cli.c rename to src/sysprof-cli/sysprof-cli.c index d9dd8413..d9b7560a 100644 --- a/src/tools/sysprof-cli.c +++ b/src/sysprof-cli/sysprof-cli.c @@ -32,18 +32,37 @@ #include -#if HAVE_POLKIT && HAVE_POLKIT_AGENT -# define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -# include -# include -# define USE_POLKIT -#endif +#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE +#include +#include #include "sysprof-capture-util-private.h" -static GMainLoop *main_loop; -static SysprofProfiler *profiler; -static int exit_code = EXIT_SUCCESS; +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureReader, sysprof_capture_reader_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) + +static GMainLoop *main_loop; +static SysprofRecording *active_recording; + +static void +diagnostics_items_changed_cb (GListModel *model, + guint position, + guint removed, + guint added, + gpointer user_data) +{ + g_assert (G_IS_LIST_MODEL (model)); + g_assert (user_data == NULL); + + for (guint i = 0; i < added; i++) + { + g_autoptr(SysprofDiagnostic) diagnostic = g_list_model_get_item (model, position+i); + + g_printerr ("%s: %s\n", + sysprof_diagnostic_get_domain (diagnostic), + sysprof_diagnostic_get_message (diagnostic)); + } +} static gboolean sigint_handler (gpointer user_data) @@ -60,8 +79,8 @@ sigint_handler (gpointer user_data) if (count == 0) { - g_printerr ("%s\n", _("Stopping profiler. Press twice more ^C to force exit.")); - sysprof_profiler_stop (profiler); + g_printerr ("%s\n", "Stopping profiler. Press twice more ^C to force exit."); + sysprof_recording_stop_async (active_recording, NULL, NULL, NULL); } count++; @@ -69,41 +88,20 @@ sigint_handler (gpointer user_data) return G_SOURCE_CONTINUE; } -static void -profiler_stopped (SysprofProfiler *profiler_, - GMainLoop *main_loop_) -{ - g_printerr ("%s\n", _("Profiler stopped.")); - g_main_loop_quit (main_loop); -} - -static void -profiler_failed (SysprofProfiler *profiler_, - const GError *reason, - GMainLoop *main_loop_) -{ - g_assert (SYSPROF_IS_PROFILER (profiler_)); - g_assert (reason != NULL); - - g_printerr ("Failure: %s\n", reason->message); - exit_code = EXIT_FAILURE; - g_main_loop_quit (main_loop_); -} - -static gint -merge_files (gint argc, - gchar **argv, +static int +merge_files (int argc, + char **argv, GOptionContext *context) { g_autoptr(SysprofCaptureWriter) writer = NULL; - g_autofree gchar *contents = NULL; - g_autofree gchar *tmpname = NULL; + g_autofree char *contents = NULL; + g_autofree char *tmpname = NULL; gsize len; - gint fd; + int fd; if (argc < 3) { - g_autofree gchar *help = NULL; + g_autofree char *help = NULL; help = g_option_context_get_help (context, FALSE, NULL); g_printerr (_("--merge requires at least 2 filename arguments")); @@ -133,7 +131,7 @@ merge_files (gint argc, return EXIT_FAILURE; } - writer = sysprof_capture_writer_new_from_fd (fd, 4096*4); + writer = sysprof_capture_writer_new_from_fd (fd, 0); for (guint i = 1; i < argc; i++) { @@ -158,7 +156,7 @@ merge_files (gint argc, if (g_file_get_contents (tmpname, &contents, &len, NULL)) { - const gchar *buf = contents; + const char *buf = contents; gsize to_write = len; while (to_write > 0) @@ -184,24 +182,76 @@ merge_files (gint argc, return EXIT_SUCCESS; } -gint -main (gint argc, - gchar *argv[]) +static void +sysprof_cli_wait_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofRecording *recording = (SysprofRecording *)object; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (user_data == NULL); + + if (!sysprof_recording_wait_finish (recording, result, &error)) + g_error ("Failed to complete recording: %s", error->message); + + g_main_loop_quit (main_loop); +} + +static void +sysprof_cli_record_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofProfiler *profiler = (SysprofProfiler *)object; + g_autoptr(SysprofRecording) recording = NULL; + g_autoptr(GListModel) diagnostics = NULL; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_PROFILER (profiler)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (user_data == NULL); + + if (!(recording = sysprof_profiler_record_finish (SYSPROF_PROFILER (object), result, &error))) + g_error ("Failed to start profiling session: %s", error->message); + + diagnostics = sysprof_recording_list_diagnostics (recording); + g_signal_connect (diagnostics, + "items-changed", + G_CALLBACK (diagnostics_items_changed_cb), + NULL); + diagnostics_items_changed_cb (diagnostics, + 0, + 0, + g_list_model_get_n_items (diagnostics), + NULL); + + sysprof_recording_wait_async (recording, + NULL, + sysprof_cli_wait_cb, + NULL); + + g_set_object (&active_recording, recording); +} + +int +main (int argc, + char *argv[]) { -#ifdef USE_POLKIT PolkitAgentListener *polkit = NULL; PolkitSubject *subject = NULL; -#endif - + g_autoptr(SysprofCaptureWriter) writer = NULL; + g_autoptr(SysprofProfiler) profiler = NULL; + g_autofree char *power_profile = NULL; g_auto(GStrv) child_argv = NULL; g_auto(GStrv) envs = NULL; - SysprofCaptureWriter *writer; - SysprofSource *source; GMainContext *main_context; GOptionContext *context; - const gchar *filename = "capture.syscap"; + const char *filename = "capture.syscap"; GError *error = NULL; - gchar *command = NULL; + char *command = NULL; gboolean gjs = FALSE; gboolean gtk = FALSE; gboolean no_battery = FALSE; @@ -220,12 +270,14 @@ main (gint argc, gboolean memprof = FALSE; gboolean merge = FALSE; gboolean speedtrack = FALSE; + g_autofd int gjs_trace_fd = -1; + g_autofd int trace_fd = -1; int pid = -1; int fd; int flags; GOptionEntry entries[] = { - { "no-throttle", 0, 0, G_OPTION_ARG_NONE, &no_throttle, N_("Disable CPU throttling while profiling") }, - { "pid", 'p', 0, G_OPTION_ARG_INT, &pid, N_("Make sysprof specific to a task"), N_("PID") }, + { "no-throttle", 0, 0, G_OPTION_ARG_NONE, &no_throttle, N_("Disable CPU throttling while profiling [Deprecated for --power-profile]") }, + { "pid", 'p', 0, G_OPTION_ARG_INT, &pid, N_("Make sysprof specific to a task [Deprecated]"), N_("PID") }, { "command", 'c', 0, G_OPTION_ARG_STRING, &command, N_("Run a command and profile the process"), N_("COMMAND") }, { "env", 'e', 0, G_OPTION_ARG_STRING_ARRAY, &envs, N_("Set environment variable for spawned process. Can be used multiple times."), N_("VAR=VALUE") }, { "force", 'f', 0, G_OPTION_ARG_NONE, &force, N_("Force overwrite the capture file") }, @@ -243,6 +295,7 @@ main (gint argc, { "memprof", 0, 0, G_OPTION_ARG_NONE, &memprof, N_("Profile memory allocations and frees") }, { "gnome-shell", 0, 0, G_OPTION_ARG_NONE, &gnome_shell, N_("Connect to org.gnome.Shell for profiler statistics") }, { "speedtrack", 0, 0, G_OPTION_ARG_NONE, &speedtrack, N_("Track performance of the applications main loop") }, + { "power-profile", 0, 0, G_OPTION_ARG_STRING, &power_profile, "Use POWER_PROFILE for duration of recording", "power-saver|balanced|performance" }, { "merge", 0, 0, G_OPTION_ARG_NONE, &merge, N_("Merge all provided *.syscap files and write to stdout") }, { "version", 0, 0, G_OPTION_ARG_NONE, &version, N_("Print the sysprof-cli version and exit") }, { NULL } @@ -270,7 +323,7 @@ main (gint argc, for (guint j = i + 1; j < argc; j++) g_ptr_array_add (ar, g_strdup (argv[j])); g_ptr_array_add (ar, NULL); - child_argv = (gchar **)g_ptr_array_free (ar, FALSE); + child_argv = (char **)g_ptr_array_free (ar, FALSE); /* Skip everything from -- beyond */ argc = i; break; @@ -303,6 +356,9 @@ Examples:\n\ return EXIT_SUCCESS; } + if (pid != -1) + g_printerr ("--pid is no longer supported and will be ignored\n"); + /* If merge is set, we aren't recording, but instead merging a bunch of * files together into a single syscap. */ @@ -311,7 +367,7 @@ Examples:\n\ if (argc > 2) { - gint i; + int i; g_printerr (_("Too many arguments were passed to sysprof-cli:")); @@ -324,7 +380,6 @@ Examples:\n\ main_loop = g_main_loop_new (NULL, FALSE); -#ifdef USE_POLKIT /* Start polkit agent so that we can elevate privileges from a TTY */ if (g_getenv ("DESKTOP_SESSION") == NULL && (subject = polkit_unix_process_new_for_owner (getpid (), 0, -1))) @@ -346,21 +401,8 @@ Examples:\n\ pkerror->message); } } -#endif - profiler = sysprof_local_profiler_new (); - - sysprof_local_profiler_set_inherit_stdin (SYSPROF_LOCAL_PROFILER (profiler), TRUE); - - g_signal_connect (profiler, - "failed", - G_CALLBACK (profiler_failed), - main_loop); - - g_signal_connect (profiler, - "stopped", - G_CALLBACK (profiler_stopped), - main_loop); + profiler = sysprof_profiler_new (); if (argc == 2) filename = argv[1]; @@ -384,11 +426,13 @@ Examples:\n\ return EXIT_FAILURE; } + writer = sysprof_capture_writer_new_from_fd (fd, 0); + if (command != NULL || child_argv != NULL) { - g_auto(GStrv) env = g_get_environ (); - g_autofree gchar *cwd = NULL; - gint child_argc; + g_autoptr(SysprofSpawnable) spawnable = sysprof_spawnable_new (); + g_auto(GStrv) current_env = g_get_environ (); + int child_argc; if (child_argv != NULL) { @@ -400,185 +444,100 @@ Examples:\n\ return EXIT_FAILURE; } - cwd = g_get_current_dir (); + sysprof_spawnable_set_cwd (spawnable, g_get_current_dir ()); + sysprof_spawnable_append_args (spawnable, (const char * const *)child_argv); + sysprof_spawnable_set_environ (spawnable, (const char * const *)current_env); if (envs != NULL) { - for (guint e = 0; envs[e]; e++) + for (guint i = 0; envs[i]; i++) { - const gchar *eq = strchr (envs[e], '='); + g_autofree char *key = NULL; + const char *eq; - if (eq == NULL) + if (!(eq = strchr (envs[i], '='))) { - env = g_environ_setenv (env, envs[e], "", TRUE); - } - else - { - g_autofree gchar *key = g_strndup (envs[e], eq - envs[e]); - env = g_environ_setenv (env, key, eq+1, TRUE); + sysprof_spawnable_setenv (spawnable, envs[i], ""); + continue; } + + key = g_strndup (envs[i], eq - envs[i]); + sysprof_spawnable_setenv (spawnable, key, eq+1); } } - sysprof_profiler_set_spawn (profiler, TRUE); - sysprof_profiler_set_spawn_cwd (profiler, cwd); - sysprof_profiler_set_spawn_argv (profiler, (const gchar * const *)child_argv); - sysprof_profiler_set_spawn_env (profiler, (const gchar * const *)env); + sysprof_profiler_set_spawnable (profiler, spawnable); + + if (gjs) + gjs_trace_fd = sysprof_spawnable_add_trace_fd (spawnable, "GJS_TRACE_FD"); + + if (use_trace_fd) + trace_fd = sysprof_spawnable_add_trace_fd (spawnable, NULL); + + if (memprof) + sysprof_spawnable_add_ld_preload (spawnable, PACKAGE_LIBDIR"/libsysprof-memory-"API_VERSION_S".so"); + + if (speedtrack) + sysprof_spawnable_add_ld_preload (spawnable, PACKAGE_LIBDIR"/libsysprof-speedtrack-"API_VERSION_S".so"); } - writer = sysprof_capture_writer_new_from_fd (fd, 0); - sysprof_profiler_set_writer (profiler, writer); + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); - if (use_trace_fd) - { - source = sysprof_tracefd_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } - - if (gjs) - { - source = sysprof_gjs_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } - - if (gtk) - { - source = sysprof_tracefd_source_new (); - sysprof_tracefd_source_set_envvar (SYSPROF_TRACEFD_SOURCE (source), "GTK_TRACE_FD"); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } - -#ifdef __linux__ - source = sysprof_proc_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); -#endif - -#ifdef __linux__ if (!no_perf) - { - source = sysprof_perf_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } -#endif + sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); if (!no_disk) - { - source = sysprof_diskstat_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); if (!no_decode) - { - /* Add __symbols__ rollup after recording to avoid loading - * symbols from the maching viewing the capture. - */ - source = sysprof_symbols_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_add_instrument (profiler, sysprof_symbols_bundle_new ()); -#ifdef __linux__ if (!no_cpu) - { - source = sysprof_hostinfo_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } -#endif + sysprof_profiler_add_instrument (profiler, sysprof_cpu_usage_new ()); -#ifdef __linux__ if (!no_memory) - { - source = sysprof_memory_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } -#endif + sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); if (!no_battery) - { - source = sysprof_battery_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_add_instrument (profiler, sysprof_battery_charge_new ()); if (rapl) - { - source = sysprof_proxy_source_new (G_BUS_TYPE_SYSTEM, - "org.gnome.Sysprof3", - "/org/gnome/Sysprof3/RAPL"); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_add_instrument (profiler, sysprof_energy_usage_new ()); if (!no_network) - { - source = sysprof_netdev_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); -#ifdef __linux__ - source = sysprof_governor_source_new (); - if (no_throttle) - sysprof_governor_source_set_disable_governor (SYSPROF_GOVERNOR_SOURCE (source), TRUE); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); -#endif + if (power_profile) + sysprof_profiler_add_instrument (profiler, sysprof_power_profile_new (power_profile)); + else if (no_throttle) + sysprof_profiler_add_instrument (profiler, sysprof_power_profile_new ("performance")); if (gnome_shell) - { - source = sysprof_proxy_source_new (G_BUS_TYPE_SESSION, - "org.gnome.Shell", - "/org/gnome/Sysprof3/Profiler"); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_add_instrument (profiler, + sysprof_proxied_instrument_new (G_BUS_TYPE_SESSION, + "org.gnome.Shell", + "/org/gnome/Sysprof3/Profiler")); - if (memprof) - { - source = sysprof_memprof_source_new (); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } + sysprof_profiler_record_async (profiler, + writer, + NULL, + sysprof_cli_record_cb, + NULL); - if (speedtrack) - { - source = g_object_new (SYSPROF_TYPE_PRELOAD_SOURCE, - "preload", "libsysprof-speedtrack-4.so", - NULL); - sysprof_profiler_add_source (profiler, source); - g_object_unref (source); - } - - if (pid != -1) - { - sysprof_profiler_set_whole_system (profiler, FALSE); - sysprof_profiler_add_pid (profiler, pid); - } - - sysprof_profiler_start (profiler); + g_unix_signal_add (SIGINT, sigint_handler, main_loop); + g_unix_signal_add (SIGTERM, sigint_handler, main_loop); g_printerr ("Recording, press ^C to exit\n"); g_main_loop_run (main_loop); + sysprof_capture_writer_flush (writer); + main_context = g_main_loop_get_context (main_loop); while (g_main_context_pending (main_context)) g_main_context_iteration (main_context, FALSE); sysprof_capture_writer_flush (writer); - g_clear_pointer (&writer, sysprof_capture_writer_unref); - g_clear_object (&profiler); - g_clear_pointer (&main_loop, g_main_loop_unref); - g_clear_pointer (&context, g_option_context_free); - return EXIT_SUCCESS; } diff --git a/src/sysprof/gtk/help-overlay.ui b/src/sysprof/gtk/help-overlay.ui index 69db015c..8c0b11ad 100644 --- a/src/sysprof/gtk/help-overlay.ui +++ b/src/sysprof/gtk/help-overlay.ui @@ -5,99 +5,13 @@ shortcuts 12 - - - Files - - - Save Recording - Saves the current recording - win.save-capture - - - - - Open recording - Opens a previously saved recording - app.open-capture - - - - - - - Recording - - - Record again - Starts a new recording - win.replay-capture - - - - - Stop recording - Escape - - - - - - - Callgraph - - - Expand function - Shows the direct descendants of the callgraph function - Right - - - - - Collapse function - Hides all callgraph descendants below the selected function - Left - - - - - Jump into function - Selects the function or file as the top of the callgraph - Return - - - - - - - Visualizers - - - Zoom in - <primary>plus - - - - - Zoom out - <primary>minus - - - - - Reset zoom - <primary>0 - - - - General Show Help - app.help + win.help @@ -106,28 +20,10 @@ win.show-help-overlay - - - New Tab - win.new-tab - - - - - Switch Tab - <alt>1...9 - - - - - New Window - app.new-window - - Close Window - win.close-tab + window.close diff --git a/src/sysprof/gtk/menus.ui b/src/sysprof/gtk/menus.ui index ea3d9ca0..335463c5 100644 --- a/src/sysprof/gtk/menus.ui +++ b/src/sysprof/gtk/menus.ui @@ -1,50 +1,22 @@ -
- - New Tab - win.new-tab - - - New Window - app.new-window - -
-
- - Open Recording… - app.open-capture - - - Save Recording… - win.save-capture - -
-
- - Record Again - win.replay-capture - -
-
- - Close - win.close-tab - -
- Keyboard Shortcuts + _Preferences + win.preferences + + + _Keyboard Shortcuts win.show-help-overlay - Help - app.help + _Help + win.help - About Sysprof - app.about + _About Sysprof + win.about
diff --git a/src/sysprof/icons/scalable/actions/address-layout-symbolic.svg b/src/sysprof/icons/scalable/actions/address-layout-symbolic.svg new file mode 100644 index 00000000..f0011c1b --- /dev/null +++ b/src/sysprof/icons/scalable/actions/address-layout-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sysprof/icons/scalable/actions/empty-symbolic.svg b/src/sysprof/icons/scalable/actions/empty-symbolic.svg new file mode 100644 index 00000000..182d5bd0 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/empty-symbolic.svg @@ -0,0 +1,35 @@ + + + + + diff --git a/src/sysprof/icons/scalable/actions/mark-chart-symbolic.svg b/src/sysprof/icons/scalable/actions/mark-chart-symbolic.svg new file mode 100644 index 00000000..6d2bb7db --- /dev/null +++ b/src/sysprof/icons/scalable/actions/mark-chart-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/sysprof/icons/scalable/actions/mark-table-symbolic.svg b/src/sysprof/icons/scalable/actions/mark-table-symbolic.svg new file mode 100644 index 00000000..aaaf54af --- /dev/null +++ b/src/sysprof/icons/scalable/actions/mark-table-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/sysprof/icons/scalable/actions/mark-waterfall-symbolic.svg b/src/sysprof/icons/scalable/actions/mark-waterfall-symbolic.svg new file mode 100644 index 00000000..0a234526 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/mark-waterfall-symbolic.svg @@ -0,0 +1,51 @@ + + + + + + + + + diff --git a/src/sysprof/icons/scalable/actions/memory-allocations-symbolic.svg b/src/sysprof/icons/scalable/actions/memory-allocations-symbolic.svg new file mode 100644 index 00000000..4f1e184b --- /dev/null +++ b/src/sysprof/icons/scalable/actions/memory-allocations-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/sysprof/icons/scalable/actions/metadata-symbolic.svg b/src/sysprof/icons/scalable/actions/metadata-symbolic.svg new file mode 100644 index 00000000..65d5d5d1 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/metadata-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/sysprof/icons/scalable/actions/process-mounts-symbolic.svg b/src/sysprof/icons/scalable/actions/process-mounts-symbolic.svg new file mode 100644 index 00000000..1910fbab --- /dev/null +++ b/src/sysprof/icons/scalable/actions/process-mounts-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/sysprof/icons/scalable/actions/storage-symbolic.svg b/src/sysprof/icons/scalable/actions/storage-symbolic.svg new file mode 100644 index 00000000..5417b9a7 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/storage-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/src/sysprof/icons/scalable/actions/system-log-symbolic.svg b/src/sysprof/icons/scalable/actions/system-log-symbolic.svg new file mode 100644 index 00000000..df132b2a --- /dev/null +++ b/src/sysprof/icons/scalable/actions/system-log-symbolic.svg @@ -0,0 +1,23 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + diff --git a/src/sysprof/icons/scalable/actions/threads-symbolic.svg b/src/sysprof/icons/scalable/actions/threads-symbolic.svg new file mode 100644 index 00000000..dffc13d6 --- /dev/null +++ b/src/sysprof/icons/scalable/actions/threads-symbolic.svg @@ -0,0 +1,50 @@ + + + + + + + + diff --git a/src/sysprof/sysprof.c b/src/sysprof/main.c similarity index 80% rename from src/sysprof/sysprof.c rename to src/sysprof/main.c index 7a8d509a..93554fef 100644 --- a/src/sysprof/sysprof.c +++ b/src/sysprof/main.c @@ -1,6 +1,6 @@ /* main.c * - * Copyright 2016 Christian Hergert + * Copyright 2016-2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,32 +14,42 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later */ #include "config.h" #include +#include + #include #include -#include + +#include #include "sysprof-application.h" -gint -main (gint argc, - gchar *argv[]) +int +main (int argc, + char *argv[]) { g_autoptr(SysprofApplication) app = NULL; gint ret; sysprof_clock_init (); + /* Ignore SIGPIPE */ + signal (SIGPIPE, SIG_IGN); + /* Set up gettext translations */ setlocale (LC_ALL, ""); bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); + g_set_prgname ("sysprof"); + app = sysprof_application_new (); ret = g_application_run (G_APPLICATION (app), argc, argv); diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build index bc5205c7..368fdd67 100644 --- a/src/sysprof/meson.build +++ b/src/sysprof/meson.build @@ -1,7 +1,65 @@ sysprof_sources = [ - 'sysprof.c', + 'main.c', 'sysprof-application.c', + 'sysprof-axis.c', + 'sysprof-callgraph-view.c', + 'sysprof-category-icon.c', + 'sysprof-chart-layer.c', + 'sysprof-chart-layer-factory.c', + 'sysprof-chart-layer-item.c', + 'sysprof-chart-layer-item-widget.c', + 'sysprof-chart.c', + 'sysprof-color-iter.c', + 'sysprof-column-layer.c', + 'sysprof-counters-section.c', + 'sysprof-cpu-section.c', + 'sysprof-css.c', + 'sysprof-duplex-layer.c', + 'sysprof-files-section.c', + 'sysprof-frame-utility.c', + 'sysprof-greeter.c', + 'sysprof-line-layer.c', + 'sysprof-logs-section.c', + 'sysprof-mark-chart-item.c', + 'sysprof-mark-chart-row.c', + 'sysprof-mark-chart.c', + 'sysprof-mark-table.c', + 'sysprof-marks-section.c', + 'sysprof-memory-callgraph-view.c', + 'sysprof-memory-section.c', + 'sysprof-metadata-section.c', + 'sysprof-normalized-series-item.c', + 'sysprof-normalized-series.c', + 'sysprof-process-dialog.c', + 'sysprof-processes-section.c', + 'sysprof-progress-cell.c', + 'sysprof-recording-pad.c', + 'sysprof-samples-section.c', + 'sysprof-scheduler.c', + 'sysprof-section.c', + 'sysprof-series.c', + 'sysprof-session-model-item.c', + 'sysprof-session-model.c', + 'sysprof-session.c', + 'sysprof-sidebar.c', + 'sysprof-single-model.c', + 'sysprof-split-layer.c', + 'sysprof-symbol-label.c', + 'sysprof-time-filter-model.c', + 'sysprof-time-label.c', + 'sysprof-time-ruler.c', + 'sysprof-time-scrubber.c', + 'sysprof-time-series-item.c', + 'sysprof-time-series.c', + 'sysprof-time-span-layer.c', + 'sysprof-traceables-utility.c', + 'sysprof-tree-expander.c', + 'sysprof-value-axis.c', + 'sysprof-weighted-callgraph-view.c', 'sysprof-window.c', + 'sysprof-xy-layer.c', + 'sysprof-xy-series-item.c', + 'sysprof-xy-series.c', ] sysprof_resources = gnome.compile_resources('sysprof-resources', 'sysprof.gresource.xml', @@ -11,11 +69,11 @@ sysprof_resources = gnome.compile_resources('sysprof-resources', 'sysprof.gresou sysprof_deps = [ cc.find_library('m', required: false), - libsysprof_capture_dep, - libsysprof_dep, - libsysprof_ui_dep, - dependency('pangoft2', required: false), - dependency('libadwaita-1', version: '>= 1.2.alpha'), + dependency('gtk4', version: gtk_req_version), + dependency('libadwaita-1', version: '>= 1.4.alpha'), + dependency('libpanel-1', version: '>= 1.3.0'), + + libsysprof_static_dep, ] sysprof = executable('sysprof', sysprof_resources + sysprof_sources, diff --git a/src/sysprof/style.css b/src/sysprof/style.css new file mode 100644 index 00000000..627e0ec0 --- /dev/null +++ b/src/sysprof/style.css @@ -0,0 +1,93 @@ +progresscell progress { + background: @accent_bg_color; + border-radius: 3px; + border: 1px solid shade(@accent_bg_color, .75); + border-right: none; +} + +progresscell { + min-height: 18px; +} + +progresscell progress:backdrop { + background: @borders; + border-color: shade(@borders, .9); +} +progresscell label:backdrop.in-progress { + color: inherit; +} + +progresscell trough { + background: alpha(@borders, .5); + border: 1px solid @borders; + border-radius: 3px; +} + +progresscell label { + font-size: 0.9em; + padding: 1px 3px; + font-feature-settings: 'tnum'; +} + +progresscell label.in-progress { + color: @accent_fg_color; +} + +callgraphview { +} + +callgraphview treeexpander indent { -gtk-icon-size: 8px; } +callgraphview treeexpander indent:nth-child(n+20) { -gtk-icon-size: 0px; } + +callgraphview symbol { + margin: -1px -3px; + padding: 1px 6px; +} +callgraphview treeexpander expander:not(.expandable) { + -gtk-icon-source: -gtk-icontheme("empty-symbolic"); +} +callgraphview row:not(:selected) treeexpander expander:checked+box symbol.process, +callgraphview row:not(:selected) treeexpander expander:checked+box symbol.thread, +callgraphview row:not(:selected) treeexpander expander:checked+box symbol.root { + border-radius: 9999px; + background-color: alpha(currentColor, .05); +} +callgraphview row:not(:selected) treeexpander symbol.context-switch { + border-radius: 9999px; + background-color: alpha(@warning_color, .1); +} +callgraphview row:not(:selected) treeexpander symbol.unwindable { + border-radius: 9999px; + background-color: alpha(@error_color, .1); +} + +timeruler { + min-height: 24px; + font-size: 0.833em; + color: alpha(currentColor, .8); +} + +timescrubber informative, +timescrubber timecode { + border-radius: 7px; + background: @accent_bg_color; + color: @accent_fg_color; + padding: 1px 3px; + margin: 2px 6px; + box-shadow: 0 2px 8px 2px alpha(black, .27); + border: 1px solid shade(@accent_bg_color, .9); + font-feature-settings: "tnum"; + font-size: .9em; +} + +.utility .view { + background: transparent; +} + +.navigation-sidebar label.indicator { + color: alpha(currentColor, .5); + border-radius: 50px; + padding: 1px 5px; + font-feature-settings: 'tnum'; + font-size: .8em; +} diff --git a/src/sysprof/sysprof-application.c b/src/sysprof/sysprof-application.c index 66ce5c25..a09422ef 100644 --- a/src/sysprof/sysprof-application.c +++ b/src/sysprof/sysprof-application.c @@ -23,6 +23,7 @@ #include "sysprof-application.h" #include "sysprof-credits.h" +#include "sysprof-greeter.h" #include "sysprof-window.h" struct _SysprofApplication @@ -32,100 +33,41 @@ struct _SysprofApplication G_DEFINE_TYPE (SysprofApplication, sysprof_application, ADW_TYPE_APPLICATION) -struct { - const gchar *action_name; - const gchar *accels[12]; -} default_accels[] = { - { "app.help", { "F1", NULL } }, - { "app.quit", { "q", NULL } }, - { "app.new-window", { "n", NULL } }, - { "app.open-capture", { "o", NULL } }, - { "zoom.zoom-in", { "plus", "KP_Add", "equal", "ZoomIn", NULL } }, - { "zoom.zoom-out", { "minus", "KP_Subtract", "ZoomOut", NULL } }, - { "zoom.zoom-one", { "0", "KP_0", NULL } }, - { "win.new-tab", { "t", NULL } }, - { "win.close-tab", { "w", NULL } }, - { "win.replay-capture", { "r", NULL } }, - { "win.save-capture", { "s", NULL } }, - { "win.switch-tab(1)", { "1", NULL } }, - { "win.switch-tab(2)", { "2", NULL } }, - { "win.switch-tab(3)", { "3", NULL } }, - { "win.switch-tab(4)", { "4", NULL } }, - { "win.switch-tab(5)", { "5", NULL } }, - { "win.switch-tab(6)", { "6", NULL } }, - { "win.switch-tab(7)", { "7", NULL } }, - { "win.switch-tab(8)", { "8", NULL } }, - { "win.switch-tab(9)", { "9", NULL } }, - { NULL } -}; - static void sysprof_application_activate (GApplication *app) { - SysprofWindow *window; - GList *windows; + GtkWidget *greeter; g_assert (GTK_IS_APPLICATION (app)); - windows = gtk_application_get_windows (GTK_APPLICATION (app)); - - for (; windows != NULL; windows = windows->next) + for (const GList *iter = gtk_application_get_windows (GTK_APPLICATION (app)); + iter != NULL; + iter = iter->next) { - if (SYSPROF_IS_WINDOW (windows->data)) + if (SYSPROF_IS_WINDOW (iter->data)) { - gtk_window_present (windows->data); + gtk_window_present (iter->data); return; } } - window = SYSPROF_WINDOW (sysprof_window_new (SYSPROF_APPLICATION (app))); - gtk_window_present (GTK_WINDOW (window)); + greeter = sysprof_greeter_new (); + gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (greeter)); + gtk_window_present (GTK_WINDOW (greeter)); } static void sysprof_application_open (GApplication *app, GFile **files, - gint n_files, - const gchar *hint) + int n_files, + const char *hint) { - GtkWidget *window; g_assert (SYSPROF_IS_APPLICATION (app)); g_assert (files != NULL || n_files == 0); - window = sysprof_window_new (SYSPROF_APPLICATION (app)); - - /* Present window before opening files so that message dialogs - * always display above the window. - */ - gtk_window_present (GTK_WINDOW (window)); - - for (gint i = 0; i < n_files; i++) - sysprof_window_open (SYSPROF_WINDOW (window), files[i]); - - if (n_files == 0) - sysprof_application_activate (app); -} - -static void -sysprof_application_startup (GApplication *application) -{ - g_autoptr(GtkCssProvider) provider = NULL; - - g_assert (SYSPROF_IS_APPLICATION (application)); - - G_APPLICATION_CLASS (sysprof_application_parent_class)->startup (application); - - provider = gtk_css_provider_new (); - gtk_css_provider_load_from_resource (provider, "/org/gnome/sysprof/theme/shared.css"); - gtk_style_context_add_provider_for_display (gdk_display_get_default (), - GTK_STYLE_PROVIDER (provider), - GTK_STYLE_PROVIDER_PRIORITY_THEME+1); - - for (guint i = 0; default_accels[i].action_name; i++) - gtk_application_set_accels_for_action (GTK_APPLICATION (application), - default_accels[i].action_name, - default_accels[i].accels); + for (guint i = 0; i < n_files; i++) + sysprof_window_open (SYSPROF_APPLICATION (app), files[i]); } static void @@ -149,7 +91,6 @@ sysprof_application_class_init (SysprofApplicationClass *klass) GtkApplicationClass *gtk_app_class = GTK_APPLICATION_CLASS (klass); app_class->open = sysprof_application_open; - app_class->startup = sysprof_application_startup; app_class->activate = sysprof_application_activate; gtk_app_class->window_added = sysprof_application_window_added; @@ -176,7 +117,7 @@ sysprof_about (GSimpleAction *action, { GtkApplication *app = user_data; GtkWindow *best_toplevel = NULL; - GList *windows; + const GList *windows; g_assert (G_IS_APPLICATION (app)); g_assert (G_IS_SIMPLE_ACTION (action)); @@ -193,12 +134,12 @@ sysprof_about (GSimpleAction *action, } } - adw_show_about_window(best_toplevel, + adw_show_about_window (best_toplevel, "application-name", _("Sysprof"), "application-icon", APP_ID_S, "version", "GNOME " SYMBOLIC_VERSION " (" PACKAGE_VERSION ")", "copyright", "Copyright 2004-2009 Søren Sandmann Pedersen\n" - "Copyright 2016-2021 Christian Hergert", + "Copyright 2016-2023 Christian Hergert", "issue-url", "https://gitlab.gnome.org/GNOME/sysprof/-/issues/new", "license-type", GTK_LICENSE_GPL_3_0, "developers", sysprof_authors, @@ -215,67 +156,24 @@ sysprof_help (GSimpleAction *action, gpointer user_data) { SysprofApplication *self = user_data; + g_autoptr(GtkUriLauncher) launcher = NULL; GtkWindow *window; g_assert (SYSPROF_IS_APPLICATION (self)); g_assert (G_IS_SIMPLE_ACTION (action)); window = gtk_application_get_active_window (GTK_APPLICATION (self)); - - gtk_show_uri (window, "help:sysprof", GDK_CURRENT_TIME); -} - -static void -sysprof_new_window (GSimpleAction *action, - GVariant *variant, - gpointer user_data) -{ - SysprofApplication *self = user_data; - SysprofWindow *window; - - g_assert (SYSPROF_IS_APPLICATION (self)); - g_assert (G_IS_SIMPLE_ACTION (action)); - g_assert (variant == NULL); - - window = SYSPROF_WINDOW (sysprof_window_new (self)); - gtk_window_present (GTK_WINDOW (window)); -} - -static void -sysprof_open_capture (GSimpleAction *action, - GVariant *variant, - gpointer user_data) -{ - GtkApplication *app = user_data; - GList *list; - - g_assert (G_IS_APPLICATION (app)); - g_assert (G_IS_SIMPLE_ACTION (action)); - g_assert (variant == NULL); - - list = gtk_application_get_windows (app); - - for (; list != NULL; list = list->next) - { - GtkWindow *window = list->data; - - if (SYSPROF_IS_WINDOW (window)) - { - sysprof_window_open_from_dialog (SYSPROF_WINDOW (window)); - break; - } - } + launcher = gtk_uri_launcher_new ("help:sysprof"); + gtk_uri_launcher_launch (launcher, window, NULL, NULL, NULL); } static void sysprof_application_init (SysprofApplication *self) { static const GActionEntry actions[] = { - { "about", sysprof_about }, - { "new-window", sysprof_new_window }, - { "open-capture", sysprof_open_capture }, - { "help", sysprof_help }, - { "quit", sysprof_quit }, + { "about", sysprof_about }, + { "help", sysprof_help }, + { "quit", sysprof_quit }, }; setlocale (LC_ALL, ""); @@ -286,7 +184,10 @@ sysprof_application_init (SysprofApplication *self) g_set_application_name (_("Sysprof")); - g_action_map_add_action_entries (G_ACTION_MAP (self), actions, G_N_ELEMENTS (actions), self); + g_action_map_add_action_entries (G_ACTION_MAP (self), + actions, + G_N_ELEMENTS (actions), + self); g_application_set_default (G_APPLICATION (self)); } diff --git a/src/sysprof/sysprof-application.h b/src/sysprof/sysprof-application.h index 4c825395..28d41c9b 100644 --- a/src/sysprof/sysprof-application.h +++ b/src/sysprof/sysprof-application.h @@ -22,7 +22,8 @@ G_BEGIN_DECLS -#define SYSPROF_TYPE_APPLICATION (sysprof_application_get_type()) +#define SYSPROF_TYPE_APPLICATION (sysprof_application_get_type()) +#define SYSPROF_APPLICATION_DEFAULT (SYSPROF_APPLICATION(g_application_get_default())) G_DECLARE_FINAL_TYPE (SysprofApplication, sysprof_application, SYSPROF, APPLICATION, AdwApplication) diff --git a/src/sysprof/sysprof-axis-private.h b/src/sysprof/sysprof-axis-private.h new file mode 100644 index 00000000..9c605318 --- /dev/null +++ b/src/sysprof/sysprof-axis-private.h @@ -0,0 +1,71 @@ +/* sysprof-axis-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-axis.h" + +G_BEGIN_DECLS + +struct _SysprofAxis +{ + GObject parent_instance; + char *title; +}; + +struct _SysprofAxisClass +{ + GObjectClass parent_class; + + void (*get_min_value) (SysprofAxis *axis, + GValue *min_value); + double (*normalize) (SysprofAxis *axis, + const GValue *value); + gboolean (*is_pathological) (SysprofAxis *axis); +}; + +#define SYSPROF_AXIS_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS(obj, SYSPROF_TYPE_AXIS, SysprofAxisClass) + +static inline void +_sysprof_axis_get_min_value (SysprofAxis *axis, + GValue *min_value) +{ + SYSPROF_AXIS_GET_CLASS (axis)->get_min_value (axis, min_value); +} + +static inline double +_sysprof_axis_normalize (SysprofAxis *axis, + const GValue *value) +{ + return SYSPROF_AXIS_GET_CLASS (axis)->normalize (axis, value); +} + +static inline double +_sysprof_axis_is_pathological (SysprofAxis *axis) +{ + if (SYSPROF_AXIS_GET_CLASS (axis)->is_pathological) + return SYSPROF_AXIS_GET_CLASS (axis)->is_pathological (axis); + + return FALSE; +} + +void _sysprof_axis_emit_range_changed (SysprofAxis *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-axis.c b/src/sysprof/sysprof-axis.c new file mode 100644 index 00000000..a93c3ea3 --- /dev/null +++ b/src/sysprof/sysprof-axis.c @@ -0,0 +1,160 @@ +/* sysprof-axis.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-axis-private.h" + +enum { + PROP_0, + PROP_TITLE, + N_PROPS +}; + +enum { + RANGE_CHANGED, + N_SIGNALS +}; + +G_DEFINE_ABSTRACT_TYPE (SysprofAxis, sysprof_axis, G_TYPE_OBJECT) + +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS]; + +static double +sysprof_axis_real_normalize (SysprofAxis *axis, + const GValue *value) +{ + return .0; +} + +static void +sysprof_axis_finalize (GObject *object) +{ + SysprofAxis *self = (SysprofAxis *)object; + + g_clear_pointer (&self->title, g_free); + + G_OBJECT_CLASS (sysprof_axis_parent_class)->finalize (object); +} + +static void +sysprof_axis_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofAxis *self = SYSPROF_AXIS (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, sysprof_axis_get_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_axis_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofAxis *self = SYSPROF_AXIS (object); + + switch (prop_id) + { + case PROP_TITLE: + sysprof_axis_set_title (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_axis_class_init (SysprofAxisClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_axis_finalize; + object_class->get_property = sysprof_axis_get_property; + object_class->set_property = sysprof_axis_set_property; + + klass->normalize = sysprof_axis_real_normalize; + + properties [PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + /** + * SysprofAxis::range-changed: + * + * This signal is emitted when the range of the axis has changed. + */ + signals[RANGE_CHANGED] = + g_signal_new ("range-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); +} + +static void +sysprof_axis_init (SysprofAxis *self) +{ +} + +const char * +sysprof_axis_get_title (SysprofAxis *self) +{ + g_return_val_if_fail (SYSPROF_IS_AXIS (self), NULL); + + return self->title; +} + +void +sysprof_axis_set_title (SysprofAxis *self, + const char *title) +{ + g_return_if_fail (SYSPROF_IS_AXIS (self)); + + if (g_set_str (&self->title, title)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +void +_sysprof_axis_emit_range_changed (SysprofAxis *self) +{ + g_return_if_fail (SYSPROF_IS_AXIS (self)); + + g_signal_emit (self, signals[RANGE_CHANGED], 0); +} diff --git a/src/sysprof/sysprof-axis.h b/src/sysprof/sysprof-axis.h new file mode 100644 index 00000000..9b57cc25 --- /dev/null +++ b/src/sysprof/sysprof-axis.h @@ -0,0 +1,44 @@ +/* sysprof-axis.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_AXIS (sysprof_axis_get_type()) +#define SYSPROF_IS_AXIS(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_AXIS)) +#define SYSPROF_AXIS(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_AXIS, SysprofAxis)) +#define SYSPROF_AXIS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_AXIS, SysprofAxisClass)) + +typedef struct _SysprofAxis SysprofAxis; +typedef struct _SysprofAxisClass SysprofAxisClass; + +GType sysprof_axis_get_type (void) G_GNUC_CONST; +const char *sysprof_axis_get_title (SysprofAxis *self); +void sysprof_axis_set_title (SysprofAxis *self, + const char *title); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofAxis, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-callgraph-view-private.h b/src/sysprof/sysprof-callgraph-view-private.h new file mode 100644 index 00000000..55443808 --- /dev/null +++ b/src/sysprof/sysprof-callgraph-view-private.h @@ -0,0 +1,71 @@ +/* sysprof-callgraph-view-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-callgraph-view.h" + +G_BEGIN_DECLS + +#define SYSPROF_CALLGRAPH_VIEW_GET_CLASS(instance) G_TYPE_INSTANCE_GET_CLASS(instance, SYSPROF_TYPE_CALLGRAPH_VIEW, SysprofCallgraphViewClass) + +struct _SysprofCallgraphView +{ + GtkWidget parent_instance; + + SysprofDocument *document; + SysprofCallgraph *callgraph; + GSignalGroup *traceables_signals; + GListModel *traceables; + GListModel *utility_traceables; + GListModel *utility_summary; + + GtkColumnView *callers_column_view; + GtkColumnView *descendants_column_view; + GtkColumnView *functions_column_view; + GtkCustomSorter *descendants_name_sorter; + GtkCustomSorter *functions_name_sorter; + GtkScrolledWindow *scrolled_window; + GtkWidget *paned; + + GCancellable *cancellable; + + guint reload_source; + + guint bottom_up : 1; + guint categorize_frames : 1; + guint include_threads : 1; + guint hide_system_libraries : 1; +}; + +struct _SysprofCallgraphViewClass +{ + GtkWidgetClass parent_class; + + gsize augment_size; + SysprofAugmentationFunc augment_func; + + void (*load) (SysprofCallgraphView *self, + SysprofCallgraph *callgraph); +}; + +G_END_DECLS diff --git a/src/sysprof/sysprof-callgraph-view.c b/src/sysprof/sysprof-callgraph-view.c new file mode 100644 index 00000000..1fa37221 --- /dev/null +++ b/src/sysprof/sysprof-callgraph-view.c @@ -0,0 +1,881 @@ +/* sysprof-callgraph-view.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-resources.h" + +#include "sysprof-callgraph-view-private.h" +#include "sysprof-category-icon.h" +#include "sysprof-symbol-label-private.h" +#include "sysprof-tree-expander.h" + +enum { + PROP_0, + PROP_BOTTOM_UP, + PROP_CATEGORIZE_FRAMES, + PROP_CALLGRAPH, + PROP_DOCUMENT, + PROP_HIDE_SYSTEM_LIBRARIES, + PROP_INCLUDE_THREADS, + PROP_TRACEABLES, + PROP_UTILITY_SUMMARY, + PROP_UTILITY_TRACEABLES, + N_PROPS +}; + +static void buildable_iface_init (GtkBuildableIface *iface); +static GListModel *sysprof_callgraph_view_create_model_func (gpointer item, + gpointer user_data); +static void descendants_selection_changed_cb (SysprofCallgraphView *self, + guint position, + guint n_items, + GtkSingleSelection *single); +static void sysprof_callgraph_view_queue_reload (SysprofCallgraphView *self); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SysprofCallgraphView, sysprof_callgraph_view, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_callgraph_view_set_utility_traceables (SysprofCallgraphView *self, + GListModel *model) +{ + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_assert (!model || G_IS_LIST_MODEL (model)); + + if (g_set_object (&self->utility_traceables, model)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UTILITY_TRACEABLES]); +} + +static void +sysprof_callgraph_view_set_utility_summary (SysprofCallgraphView *self, + GListModel *model) +{ + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_assert (!model || G_IS_LIST_MODEL (model)); + + if (g_set_object (&self->utility_summary, model)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UTILITY_SUMMARY]); +} + +static void +sysprof_callgraph_view_set_descendants (SysprofCallgraphView *self, + GListModel *model) +{ + g_autoptr(GtkTreeListRowSorter) descendants_sorter = NULL; + g_autoptr(GtkSingleSelection) descendants_selection = NULL; + g_autoptr(GtkSortListModel) descendants_sort_model = NULL; + g_autoptr(GtkTreeListModel) descendants_tree = NULL; + g_autoptr(GtkTreeListRow) descendants_first = NULL; + GtkSorter *column_sorter; + + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_assert (G_IS_LIST_MODEL (model)); + + column_sorter = gtk_column_view_get_sorter (self->descendants_column_view); + descendants_tree = gtk_tree_list_model_new (g_object_ref (model), + FALSE, FALSE, + sysprof_callgraph_view_create_model_func, + NULL, NULL); + descendants_sorter = gtk_tree_list_row_sorter_new (g_object_ref (column_sorter)); + descendants_sort_model = gtk_sort_list_model_new (g_object_ref (G_LIST_MODEL (descendants_tree)), + g_object_ref (GTK_SORTER (descendants_sorter))); + descendants_selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (descendants_sort_model))); + g_signal_connect_object (descendants_selection, + "selection-changed", + G_CALLBACK (descendants_selection_changed_cb), + self, + G_CONNECT_SWAPPED); + gtk_column_view_set_model (self->descendants_column_view, + GTK_SELECTION_MODEL (descendants_selection)); + + if ((descendants_first = gtk_tree_list_model_get_row (descendants_tree, 0))) + gtk_tree_list_row_set_expanded (descendants_first, TRUE); + +} + +static void +sysprof_callgraph_view_descendants_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofCallgraph *callgraph = (SysprofCallgraph *)object; + g_autoptr(SysprofCallgraphView) self = user_data; + g_autoptr(GListModel) model = NULL; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + if ((model = sysprof_callgraph_descendants_finish (callgraph, result, &error))) + sysprof_callgraph_view_set_descendants (self, model); +} + +static void +callers_selection_changed_cb (SysprofCallgraphView *self, + guint position, + guint n_items, + GtkSingleSelection *single) +{ + GObject *object; + + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_assert (GTK_IS_SINGLE_SELECTION (single)); + + if ((object = gtk_single_selection_get_selected_item (single))) + { + SysprofCallgraphSymbol *sym = SYSPROF_CALLGRAPH_SYMBOL (object); + SysprofSymbol *symbol = sysprof_callgraph_symbol_get_symbol (sym); + + g_debug ("Select %s as root callgraph node", + sysprof_symbol_get_name (symbol)); + + sysprof_callgraph_descendants_async (self->callgraph, + symbol, + NULL, + sysprof_callgraph_view_descendants_cb, + g_object_ref (self)); + } +} + +static void +sysprof_callgraph_view_list_traceables_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofCallgraphFrame *frame = (SysprofCallgraphFrame *)object; + g_autoptr(SysprofCallgraphView) self = user_data; + g_autoptr(GListModel) model = NULL; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_CALLGRAPH_FRAME (frame)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + model = sysprof_callgraph_frame_list_traceables_finish (frame, result, &error); + + sysprof_callgraph_view_set_utility_traceables (self, model); +} + +static void +sysprof_callgraph_view_summarize_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofCallgraphFrame *frame = (SysprofCallgraphFrame *)object; + g_autoptr(SysprofCallgraphView) self = user_data; + g_autoptr(GListModel) model = NULL; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_CALLGRAPH_FRAME (frame)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + model = sysprof_callgraph_frame_summarize_finish (frame, result, &error); + + sysprof_callgraph_view_set_utility_summary (self, model); +} + +static void +descendants_selection_changed_cb (SysprofCallgraphView *self, + guint position, + guint n_items, + GtkSingleSelection *single) +{ + g_autoptr(GObject) item = NULL; + GObject *object; + + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_assert (GTK_IS_SINGLE_SELECTION (single)); + + sysprof_callgraph_view_set_utility_summary (self, NULL); + sysprof_callgraph_view_set_utility_traceables (self, NULL); + + if ((object = gtk_single_selection_get_selected_item (single)) && + GTK_IS_TREE_LIST_ROW (object) && + (item = gtk_tree_list_row_get_item (GTK_TREE_LIST_ROW (object))) && + SYSPROF_IS_CALLGRAPH_FRAME (item)) + { + sysprof_callgraph_frame_summarize_async (SYSPROF_CALLGRAPH_FRAME (item), + NULL, + sysprof_callgraph_view_summarize_cb, + g_object_ref (self)); + sysprof_callgraph_frame_list_traceables_async (SYSPROF_CALLGRAPH_FRAME (item), + NULL, + sysprof_callgraph_view_list_traceables_cb, + g_object_ref (self)); + } +} + +static void +functions_selection_changed_cb (SysprofCallgraphView *self, + guint position, + guint n_items, + GtkSingleSelection *single) +{ + GObject *object; + + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_assert (GTK_IS_SINGLE_SELECTION (single)); + + if ((object = gtk_single_selection_get_selected_item (single))) + { + SysprofCallgraphSymbol *sym = SYSPROF_CALLGRAPH_SYMBOL (object); + SysprofSymbol *symbol = sysprof_callgraph_symbol_get_symbol (sym); + g_autoptr(GtkSortListModel) callers_sort_model = NULL; + g_autoptr(GtkSingleSelection) callers_selection = NULL; + g_autoptr(GListModel) callers = sysprof_callgraph_list_callers (self->callgraph, symbol); + GtkSorter *column_sorter; + + column_sorter = gtk_column_view_get_sorter (self->callers_column_view); + callers_sort_model = gtk_sort_list_model_new (g_object_ref (callers), + g_object_ref (column_sorter)); + callers_selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (callers_sort_model))); + gtk_single_selection_set_autoselect (callers_selection, FALSE); + gtk_single_selection_set_selected (callers_selection, GTK_INVALID_LIST_POSITION); + g_signal_connect_object (callers_selection, + "selection-changed", + G_CALLBACK (callers_selection_changed_cb), + self, + G_CONNECT_SWAPPED); + gtk_column_view_set_model (self->callers_column_view, + GTK_SELECTION_MODEL (callers_selection)); + + switch (sysprof_symbol_get_kind (symbol)) + { + case SYSPROF_SYMBOL_KIND_ROOT: + sysprof_callgraph_view_set_descendants (self, G_LIST_MODEL (self->callgraph)); + break; + + default: + case SYSPROF_SYMBOL_KIND_PROCESS: + case SYSPROF_SYMBOL_KIND_THREAD: + case SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH: + case SYSPROF_SYMBOL_KIND_UNWINDABLE: + case SYSPROF_SYMBOL_KIND_USER: + case SYSPROF_SYMBOL_KIND_KERNEL: + sysprof_callgraph_descendants_async (self->callgraph, + symbol, + NULL, + sysprof_callgraph_view_descendants_cb, + g_object_ref (self)); + break; + } + } + else + { + gtk_column_view_set_model (self->callers_column_view, NULL); + } +} + +static void +make_descendant_root_action (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofCallgraphView *self = (SysprofCallgraphView *)widget; + g_autoptr(SysprofCallgraphFrame) frame = NULL; + g_autoptr(GtkTreeListRow) row = NULL; + GtkSelectionModel *model; + + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + if ((model = gtk_column_view_get_model (self->descendants_column_view)) && + GTK_IS_SINGLE_SELECTION (model) && + (row = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (model))) && + GTK_IS_TREE_LIST_ROW (row) && + (frame = gtk_tree_list_row_get_item (row)) && + SYSPROF_IS_CALLGRAPH_FRAME (frame)) + { + SysprofSymbol *symbol = sysprof_callgraph_frame_get_symbol (frame); + + if (sysprof_symbol_get_kind (symbol) != SYSPROF_SYMBOL_KIND_ROOT) + sysprof_callgraph_descendants_async (self->callgraph, + symbol, + NULL, + sysprof_callgraph_view_descendants_cb, + g_object_ref (self)); + } +} + +static void +sysprof_callgraph_view_dispose (GObject *object) +{ + SysprofCallgraphView *self = (SysprofCallgraphView *)object; + + if (self->traceables_signals) + { + g_signal_group_set_target (self->traceables_signals, NULL); + g_clear_object (&self->traceables_signals); + } + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_CALLGRAPH_VIEW); + + g_clear_handle_id (&self->reload_source, g_source_remove); + + g_clear_pointer (&self->paned, gtk_widget_unparent); + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + + g_clear_object (&self->callgraph); + g_clear_object (&self->document); + g_clear_object (&self->traceables); + g_clear_object (&self->utility_summary); + g_clear_object (&self->utility_traceables); + + G_OBJECT_CLASS (sysprof_callgraph_view_parent_class)->dispose (object); +} + +static void +sysprof_callgraph_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofCallgraphView *self = SYSPROF_CALLGRAPH_VIEW (object); + + switch (prop_id) + { + case PROP_BOTTOM_UP: + g_value_set_boolean (value, sysprof_callgraph_view_get_bottom_up (self)); + break; + + case PROP_CATEGORIZE_FRAMES: + g_value_set_boolean (value, sysprof_callgraph_view_get_categorize_frames (self)); + break; + + case PROP_CALLGRAPH: + g_value_set_object (value, sysprof_callgraph_view_get_callgraph (self)); + break; + + case PROP_DOCUMENT: + g_value_set_object (value, sysprof_callgraph_view_get_document (self)); + break; + + case PROP_HIDE_SYSTEM_LIBRARIES: + g_value_set_boolean (value, sysprof_callgraph_view_get_hide_system_libraries (self)); + break; + + case PROP_INCLUDE_THREADS: + g_value_set_boolean (value, sysprof_callgraph_view_get_include_threads (self)); + break; + + case PROP_TRACEABLES: + g_value_set_object (value, sysprof_callgraph_view_get_traceables (self)); + break; + + case PROP_UTILITY_SUMMARY: + g_value_set_object (value, self->utility_summary); + break; + + case PROP_UTILITY_TRACEABLES: + g_value_set_object (value, self->utility_traceables); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_callgraph_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofCallgraphView *self = SYSPROF_CALLGRAPH_VIEW (object); + + switch (prop_id) + { + case PROP_BOTTOM_UP: + sysprof_callgraph_view_set_bottom_up (self, g_value_get_boolean (value)); + break; + + case PROP_CATEGORIZE_FRAMES: + sysprof_callgraph_view_set_categorize_frames (self, g_value_get_boolean (value)); + break; + + case PROP_DOCUMENT: + sysprof_callgraph_view_set_document (self, g_value_get_object (value)); + break; + + case PROP_HIDE_SYSTEM_LIBRARIES: + sysprof_callgraph_view_set_hide_system_libraries (self, g_value_get_boolean (value)); + break; + + case PROP_INCLUDE_THREADS: + sysprof_callgraph_view_set_include_threads (self, g_value_get_boolean (value)); + break; + + case PROP_TRACEABLES: + sysprof_callgraph_view_set_traceables (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_callgraph_view_class_init (SysprofCallgraphViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_callgraph_view_dispose; + object_class->get_property = sysprof_callgraph_view_get_property; + object_class->set_property = sysprof_callgraph_view_set_property; + + properties[PROP_BOTTOM_UP] = + g_param_spec_boolean ("bottom-up", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_CATEGORIZE_FRAMES] = + g_param_spec_boolean ("categorize-frames", NULL, NULL, + TRUE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_CALLGRAPH] = + g_param_spec_object ("callgraph", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_DOCUMENT] = + g_param_spec_object ("document", NULL, NULL, + SYSPROF_TYPE_DOCUMENT, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_HIDE_SYSTEM_LIBRARIES] = + g_param_spec_boolean ("hide-system-libraries", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_INCLUDE_THREADS] = + g_param_spec_boolean ("include-threads", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TRACEABLES] = + g_param_spec_object ("traceables", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_UTILITY_SUMMARY] = + g_param_spec_object ("utility-summary", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_UTILITY_TRACEABLES] = + g_param_spec_object ("utility-traceables", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-callgraph-view.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_css_name (widget_class, "callgraphview"); + + gtk_widget_class_bind_template_child (widget_class, SysprofCallgraphView, callers_column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofCallgraphView, descendants_column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofCallgraphView, descendants_name_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofCallgraphView, functions_column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofCallgraphView, functions_name_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofCallgraphView, paned); + + gtk_widget_class_install_action (widget_class, "callgraph.make-descendant-root", NULL, make_descendant_root_action); + + klass->augment_size = GLIB_SIZEOF_VOID_P; + + g_resources_register (sysprof_get_resource ()); + + g_type_ensure (PANEL_TYPE_PANED); + g_type_ensure (SYSPROF_TYPE_CATEGORY_ICON); + g_type_ensure (SYSPROF_TYPE_SYMBOL_LABEL); + g_type_ensure (SYSPROF_TYPE_TREE_EXPANDER); +} + +static void +sysprof_callgraph_view_init (SysprofCallgraphView *self) +{ + self->categorize_frames = TRUE; + + self->traceables_signals = g_signal_group_new (G_TYPE_LIST_MODEL); + g_signal_connect_object (self->traceables_signals, + "bind", + G_CALLBACK (sysprof_callgraph_view_queue_reload), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->traceables_signals, + "items-changed", + G_CALLBACK (sysprof_callgraph_view_queue_reload), + self, + G_CONNECT_SWAPPED); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static int +descendants_name_compare (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; + SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; + + return g_strcmp0 (sysprof_symbol_get_name (sysprof_callgraph_frame_get_symbol (frame_a)), + sysprof_symbol_get_name (sysprof_callgraph_frame_get_symbol (frame_b))); +} + +static int +functions_name_compare (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphSymbol *sym_a = (SysprofCallgraphSymbol *)a; + SysprofCallgraphSymbol *sym_b = (SysprofCallgraphSymbol *)b; + + return g_strcmp0 (sysprof_symbol_get_name (sysprof_callgraph_symbol_get_symbol (sym_a)), + sysprof_symbol_get_name (sysprof_callgraph_symbol_get_symbol (sym_b))); +} + +static GListModel * +sysprof_callgraph_view_create_model_func (gpointer item, + gpointer user_data) +{ + if (g_list_model_get_n_items (G_LIST_MODEL (item)) > 0) + return g_object_ref (G_LIST_MODEL (item)); + + return NULL; +} + +static void +sysprof_callgraph_view_reload_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofDocument *document = (SysprofDocument *)object; + g_autoptr(SysprofCallgraphView) self = user_data; + g_autoptr(SysprofCallgraph) callgraph = NULL; + g_autoptr(GError) error = NULL; + GtkSorter *column_sorter; + + g_autoptr(GtkSingleSelection) functions_selection = NULL; + g_autoptr(GtkSortListModel) functions_sort_model = NULL; + g_autoptr(GListModel) functions_model = NULL; + + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + if (!(callgraph = sysprof_document_callgraph_finish (document, result, &error))) + { + g_debug ("Failed to generate callgraph: %s", error->message); + return; + } + + g_set_object (&self->callgraph, callgraph); + + sysprof_callgraph_view_set_descendants (self, G_LIST_MODEL (callgraph)); + + column_sorter = gtk_column_view_get_sorter (self->functions_column_view); + functions_model = sysprof_callgraph_list_symbols (callgraph); + functions_sort_model = gtk_sort_list_model_new (g_object_ref (functions_model), + g_object_ref (column_sorter)); + functions_selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (functions_sort_model))); + gtk_single_selection_set_autoselect (functions_selection, FALSE); + gtk_single_selection_set_can_unselect (functions_selection, TRUE); + gtk_single_selection_set_selected (functions_selection, GTK_INVALID_LIST_POSITION); + g_signal_connect_object (functions_selection, + "selection-changed", + G_CALLBACK (functions_selection_changed_cb), + self, + G_CONNECT_SWAPPED); + gtk_column_view_set_model (self->functions_column_view, + GTK_SELECTION_MODEL (functions_selection)); + + gtk_custom_sorter_set_sort_func (self->descendants_name_sorter, + descendants_name_compare, NULL, NULL); + gtk_custom_sorter_set_sort_func (self->functions_name_sorter, + functions_name_compare, NULL, NULL); + + if (SYSPROF_CALLGRAPH_VIEW_GET_CLASS (self)->load) + SYSPROF_CALLGRAPH_VIEW_GET_CLASS (self)->load (self, callgraph); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CALLGRAPH]); +} + +static gboolean +sysprof_callgraph_view_reload (SysprofCallgraphView *self) +{ + SysprofCallgraphFlags flags = 0; + + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + g_clear_handle_id (&self->reload_source, g_source_remove); + + if (self->include_threads) + flags |= SYSPROF_CALLGRAPH_FLAGS_INCLUDE_THREADS; + + if (self->hide_system_libraries) + flags |= SYSPROF_CALLGRAPH_FLAGS_HIDE_SYSTEM_LIBRARIES; + + if (self->bottom_up) + flags |= SYSPROF_CALLGRAPH_FLAGS_BOTTOM_UP; + + if (self->categorize_frames) + flags |= SYSPROF_CALLGRAPH_FLAGS_CATEGORIZE_FRAMES; + + sysprof_document_callgraph_async (self->document, + flags, + self->traceables, + SYSPROF_CALLGRAPH_VIEW_GET_CLASS (self)->augment_size, + SYSPROF_CALLGRAPH_VIEW_GET_CLASS (self)->augment_func, + NULL, + NULL, + self->cancellable, + sysprof_callgraph_view_reload_cb, + g_object_ref (self)); + + return G_SOURCE_REMOVE; +} + +static void +sysprof_callgraph_view_queue_reload (SysprofCallgraphView *self) +{ + g_assert (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + sysprof_callgraph_view_set_utility_traceables (self, NULL); + + gtk_column_view_set_model (self->descendants_column_view, NULL); + gtk_column_view_set_model (self->functions_column_view, NULL); + gtk_column_view_set_model (self->callers_column_view, NULL); + + g_clear_handle_id (&self->reload_source, g_source_remove); + g_cancellable_cancel (self->cancellable); + + g_clear_object (&self->cancellable); + self->cancellable = g_cancellable_new (); + + if (self->document != NULL && self->traceables != NULL) + self->reload_source = g_idle_add_full (G_PRIORITY_LOW, + (GSourceFunc)sysprof_callgraph_view_reload, + g_object_ref (self), + g_object_unref); +} + +/** + * sysprof_callgraph_view_get_document: + * @self: a #SysprofCallgraphView + * + * Gets the document for the callgraph. + * + * Returns: (transfer none) (nullable): a #SysprofDocument or %NULL + */ +SysprofDocument * +sysprof_callgraph_view_get_document (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), NULL); + + return self->document; +} + +void +sysprof_callgraph_view_set_document (SysprofCallgraphView *self, + SysprofDocument *document) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + if (g_set_object (&self->document, document)) + { + sysprof_callgraph_view_queue_reload (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DOCUMENT]); + } +} + +/** + * sysprof_callgraph_view_get_traceables: + * @self: a #SysprofCallgraphView + * + * Gets the list of traceables to be displayed. + * + * Returns: (transfer none) (nullable): a #GListModel or %NULL + */ +GListModel * +sysprof_callgraph_view_get_traceables (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), NULL); + + return self->traceables; +} + +void +sysprof_callgraph_view_set_traceables (SysprofCallgraphView *self, + GListModel *traceables) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + g_return_if_fail (!traceables || G_IS_LIST_MODEL (traceables)); + + if (g_set_object (&self->traceables, traceables)) + { + g_signal_group_set_target (self->traceables_signals, traceables); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TRACEABLES]); + } +} + +static GObject * +sysprof_callgraph_view_get_internal_child (GtkBuildable *buildable, + GtkBuilder *builder, + const char *name) +{ + if (g_strcmp0 (name, "callers_column_view") == 0) + return G_OBJECT (SYSPROF_CALLGRAPH_VIEW (buildable)->callers_column_view); + else if (g_strcmp0 (name, "descendants_column_view") == 0) + return G_OBJECT (SYSPROF_CALLGRAPH_VIEW (buildable)->descendants_column_view); + else if (g_strcmp0 (name, "functions_column_view") == 0) + return G_OBJECT (SYSPROF_CALLGRAPH_VIEW (buildable)->functions_column_view); + + return NULL; +} + +static void +buildable_iface_init (GtkBuildableIface *iface) +{ + iface->get_internal_child = sysprof_callgraph_view_get_internal_child; +} + +/** + * sysprof_callgraph_view_get_callgraph: + * @self: a #SysprofCallgraphView + * + * Gets the callgraph being displayed. + * + * Returns: (transfer none) (nullable): a #SysprofCallgraph or %NULL + */ +SysprofCallgraph * +sysprof_callgraph_view_get_callgraph (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), NULL); + + return self->callgraph; +} + +gboolean +sysprof_callgraph_view_get_bottom_up (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), FALSE); + + return self->bottom_up; +} + +void +sysprof_callgraph_view_set_bottom_up (SysprofCallgraphView *self, + gboolean bottom_up) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + bottom_up = !!bottom_up; + + if (self->bottom_up != bottom_up) + { + self->bottom_up = bottom_up; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BOTTOM_UP]); + sysprof_callgraph_view_queue_reload (self); + } +} + +gboolean +sysprof_callgraph_view_get_categorize_frames (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), FALSE); + + return self->categorize_frames; +} + +void +sysprof_callgraph_view_set_categorize_frames (SysprofCallgraphView *self, + gboolean categorize_frames) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + categorize_frames = !!categorize_frames; + + if (self->categorize_frames != categorize_frames) + { + self->categorize_frames = categorize_frames; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CATEGORIZE_FRAMES]); + sysprof_callgraph_view_queue_reload (self); + } +} + +gboolean +sysprof_callgraph_view_get_hide_system_libraries (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), FALSE); + + return self->hide_system_libraries; +} + +void +sysprof_callgraph_view_set_hide_system_libraries (SysprofCallgraphView *self, + gboolean hide_system_libraries) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + hide_system_libraries = !!hide_system_libraries; + + if (self->hide_system_libraries != hide_system_libraries) + { + self->hide_system_libraries = hide_system_libraries; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HIDE_SYSTEM_LIBRARIES]); + sysprof_callgraph_view_queue_reload (self); + } +} + +gboolean +sysprof_callgraph_view_get_include_threads (SysprofCallgraphView *self) +{ + g_return_val_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self), FALSE); + + return self->include_threads; +} + +void +sysprof_callgraph_view_set_include_threads (SysprofCallgraphView *self, + gboolean include_threads) +{ + g_return_if_fail (SYSPROF_IS_CALLGRAPH_VIEW (self)); + + include_threads = !!include_threads; + + if (self->include_threads != include_threads) + { + self->include_threads = include_threads; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INCLUDE_THREADS]); + sysprof_callgraph_view_queue_reload (self); + } +} diff --git a/src/sysprof/sysprof-callgraph-view.h b/src/sysprof/sysprof-callgraph-view.h new file mode 100644 index 00000000..96e28dd8 --- /dev/null +++ b/src/sysprof/sysprof-callgraph-view.h @@ -0,0 +1,61 @@ +/* sysprof-callgraph-view.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CALLGRAPH_VIEW (sysprof_callgraph_view_get_type()) +#define SYSPROF_IS_CALLGRAPH_VIEW(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_CALLGRAPH_VIEW) +#define SYSPROF_CALLGRAPH_VIEW(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_CALLGRAPH_VIEW, SysprofCallgraphView) +#define SYSPROF_CALLGRAPH_VIEW_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_CALLGRAPH_VIEW, SysprofCallgraphViewClass) + +typedef struct _SysprofCallgraphView SysprofCallgraphView; +typedef struct _SysprofCallgraphViewClass SysprofCallgraphViewClass; + +GType sysprof_callgraph_view_get_type (void) G_GNUC_CONST; +SysprofCallgraph *sysprof_callgraph_view_get_callgraph (SysprofCallgraphView *self); +SysprofDocument *sysprof_callgraph_view_get_document (SysprofCallgraphView *self); +void sysprof_callgraph_view_set_document (SysprofCallgraphView *self, + SysprofDocument *document); +GListModel *sysprof_callgraph_view_get_traceables (SysprofCallgraphView *self); +void sysprof_callgraph_view_set_traceables (SysprofCallgraphView *self, + GListModel *model); +gboolean sysprof_callgraph_view_get_bottom_up (SysprofCallgraphView *self); +void sysprof_callgraph_view_set_bottom_up (SysprofCallgraphView *self, + gboolean bottom_up); +gboolean sysprof_callgraph_view_get_categorize_frames (SysprofCallgraphView *self); +void sysprof_callgraph_view_set_categorize_frames (SysprofCallgraphView *self, + gboolean categorize_frames); +gboolean sysprof_callgraph_view_get_include_threads (SysprofCallgraphView *self); +void sysprof_callgraph_view_set_include_threads (SysprofCallgraphView *self, + gboolean include_threads); +gboolean sysprof_callgraph_view_get_hide_system_libraries (SysprofCallgraphView *self); +void sysprof_callgraph_view_set_hide_system_libraries (SysprofCallgraphView *self, + gboolean hide_system_libraries); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCallgraphView, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-callgraph-view.ui b/src/sysprof/sysprof-callgraph-view.ui new file mode 100644 index 00000000..c86058b8 --- /dev/null +++ b/src/sysprof/sysprof-callgraph-view.ui @@ -0,0 +1,268 @@ + + + + diff --git a/src/sysprof/sysprof-category-icon.c b/src/sysprof/sysprof-category-icon.c new file mode 100644 index 00000000..42b81dd3 --- /dev/null +++ b/src/sysprof/sysprof-category-icon.c @@ -0,0 +1,168 @@ +/* sysprof-category-icon.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-category-icon.h" + +struct _SysprofCategoryIcon +{ + GtkWidget parent_instance; + SysprofCallgraphCategory category; +}; + +enum { + PROP_0, + PROP_CATEGORY, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofCategoryIcon, sysprof_category_icon, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; +static GdkRGBA category_colors[SYSPROF_CALLGRAPH_CATEGORY_LAST]; + +static void +sysprof_category_icon_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofCategoryIcon *self = (SysprofCategoryIcon *)widget; + + g_assert (SYSPROF_IS_CATEGORY_ICON (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + if (self->category >= G_N_ELEMENTS (category_colors)) + return; + + if (category_colors[self->category].alpha == 0) + return; + + gtk_snapshot_append_color (snapshot, + &category_colors[self->category], + &GRAPHENE_RECT_INIT (0, 0, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget))); +} + +static void +sysprof_category_icon_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofCategoryIcon *self = SYSPROF_CATEGORY_ICON (object); + + switch (prop_id) + { + case PROP_CATEGORY: + g_value_set_enum (value, sysprof_category_icon_get_category (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_category_icon_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofCategoryIcon *self = SYSPROF_CATEGORY_ICON (object); + + switch (prop_id) + { + case PROP_CATEGORY: + sysprof_category_icon_set_category (self, g_value_get_enum (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_category_icon_class_init (SysprofCategoryIconClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = sysprof_category_icon_get_property; + object_class->set_property = sysprof_category_icon_set_property; + + widget_class->snapshot = sysprof_category_icon_snapshot; + + properties[PROP_CATEGORY] = + g_param_spec_enum ("category", NULL, NULL, + SYSPROF_TYPE_CALLGRAPH_CATEGORY, + SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_A11Y], "#000"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_ACTIONS], "#f66151"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_CONTEXT_SWITCH], "#ffbe6f"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_CSS], "#62a0ea"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_GRAPHICS], "#ed333b"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_ICONS], "#613583"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_INPUT], "#1a5fb4"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_IO], "#cdab8f"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_IPC], "#e5a50a"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_JAVASCRIPT], "#1c71d8"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_KERNEL], "#a51d2d"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_LAYOUT], "#9141ac"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_LOCKING], "#f5c211"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_MAIN_LOOP], "#5e5c64"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_MEMORY], "#f9f06b"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_PAINT], "#2ec27e"); + gdk_rgba_parse (&category_colors[SYSPROF_CALLGRAPH_CATEGORY_WINDOWING], "#c64600"); +} + +static void +sysprof_category_icon_init (SysprofCategoryIcon *self) +{ + self->category = SYSPROF_CALLGRAPH_CATEGORY_UNCATEGORIZED; +} + +SysprofCallgraphCategory +sysprof_category_icon_get_category (SysprofCategoryIcon *self) +{ + g_return_val_if_fail (SYSPROF_IS_CATEGORY_ICON (self), 0); + + return self->category; +} + +void +sysprof_category_icon_set_category (SysprofCategoryIcon *self, + SysprofCallgraphCategory category) +{ + g_return_if_fail (SYSPROF_IS_CATEGORY_ICON (self)); + g_return_if_fail (category > 0); + g_return_if_fail (category < SYSPROF_CALLGRAPH_CATEGORY_LAST); + + if (self->category == category) + return; + + self->category = category; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CATEGORY]); + gtk_widget_queue_draw (GTK_WIDGET (self)); +} diff --git a/src/libsysprof-ui/sysprof-aid-icon.h b/src/sysprof/sysprof-category-icon.h similarity index 59% rename from src/libsysprof-ui/sysprof-aid-icon.h rename to src/sysprof/sysprof-category-icon.h index 20f53f68..35263483 100644 --- a/src/libsysprof-ui/sysprof-aid-icon.h +++ b/src/sysprof/sysprof-category-icon.h @@ -1,6 +1,6 @@ -/* sysprof-aid-icon.h +/* sysprof-category-icon.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,19 +21,17 @@ #pragma once #include -#include -#include "sysprof-aid.h" +#include G_BEGIN_DECLS -#define SYSPROF_TYPE_AID_ICON (sysprof_aid_icon_get_type()) +#define SYSPROF_TYPE_CATEGORY_ICON (sysprof_category_icon_get_type()) -G_DECLARE_FINAL_TYPE (SysprofAidIcon, sysprof_aid_icon, SYSPROF, AID_ICON, GtkFlowBoxChild) +G_DECLARE_FINAL_TYPE (SysprofCategoryIcon, sysprof_category_icon, SYSPROF, CATEGORY_ICON, GtkWidget) -GtkWidget *sysprof_aid_icon_new (SysprofAid *aid); -SysprofAid *sysprof_aid_icon_get_aid (SysprofAidIcon *self); -void sysprof_aid_icon_toggle (SysprofAidIcon *self); -gboolean sysprof_aid_icon_is_selected (SysprofAidIcon *self); +SysprofCallgraphCategory sysprof_category_icon_get_category (SysprofCategoryIcon *self); +void sysprof_category_icon_set_category (SysprofCategoryIcon *self, + SysprofCallgraphCategory category); G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-counters-aid.h b/src/sysprof/sysprof-chart-layer-factory-private.h similarity index 69% rename from src/libsysprof-ui/sysprof-counters-aid.h rename to src/sysprof/sysprof-chart-layer-factory-private.h index 5541b60f..a8add5e6 100644 --- a/src/libsysprof-ui/sysprof-counters-aid.h +++ b/src/sysprof/sysprof-chart-layer-factory-private.h @@ -1,6 +1,6 @@ -/* sysprof-counters-aid.h +/* sysprof-chart-layer-factory-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,12 @@ #pragma once -#include "sysprof-aid.h" +#include "sysprof-chart-layer-factory.h" +#include "sysprof-chart-layer-item.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_COUNTERS_AID (sysprof_counters_aid_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofCountersAid, sysprof_counters_aid, SYSPROF, COUNTERS_AID, SysprofAid) - -SysprofAid *sysprof_counters_aid_new (void); +void _sysprof_chart_layer_factory_setup (SysprofChartLayerFactory *self, + SysprofChartLayerItem *item); G_END_DECLS diff --git a/src/sysprof/sysprof-chart-layer-factory.c b/src/sysprof/sysprof-chart-layer-factory.c new file mode 100644 index 00000000..5244bc00 --- /dev/null +++ b/src/sysprof/sysprof-chart-layer-factory.c @@ -0,0 +1,217 @@ +/* sysprof-chart-layer-factory.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart-layer-factory.h" +#include "sysprof-chart-layer-item.h" + +struct _SysprofChartLayerFactory +{ + GObject parent_instance; + GtkBuilderScope *scope; + char *resource; + GBytes *bytes; +}; + +enum { + PROP_0, + PROP_BYTES, + PROP_RESOURCE, + PROP_SCOPE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofChartLayerFactory, sysprof_chart_layer_factory, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +void +_sysprof_chart_layer_factory_setup (SysprofChartLayerFactory *self, + SysprofChartLayerItem *item) +{ + g_autoptr(GtkBuilder) builder = NULL; + g_autoptr(GError) error = NULL; + + g_return_if_fail (SYSPROF_IS_CHART_LAYER_FACTORY (self)); + g_return_if_fail (G_IS_OBJECT (item)); + g_return_if_fail (self->bytes != NULL); + + builder = gtk_builder_new (); + + gtk_builder_set_current_object (builder, G_OBJECT (item)); + + if (self->scope) + gtk_builder_set_scope (builder, self->scope); + + /* XXX: This is private API, we might need it + * gtk_builder_set_allow_template_parents (builder, TRUE); + */ + + if (!gtk_builder_extend_with_template (builder, G_OBJECT (item), G_OBJECT_TYPE (item), + (const char *)g_bytes_get_data (self->bytes, NULL), + g_bytes_get_size (self->bytes), + &error)) + g_critical ("Error building template for chart layer: %s", error->message); +} + +static void +sysprof_chart_layer_factory_constructed (GObject *object) +{ + SysprofChartLayerFactory *self = (SysprofChartLayerFactory *)object; + + G_OBJECT_CLASS (sysprof_chart_layer_factory_parent_class)->constructed (object); + + if (self->resource != NULL && self->bytes == NULL) + { + self->bytes = g_resources_lookup_data (self->resource, 0, NULL); + + if (self->bytes == NULL) + g_warning ("Failed to locate resource at %s", self->resource); + } + + if (self->bytes == NULL) + g_warning ("SysprofChartLayerFactory created without a template"); +} + +static void +sysprof_chart_layer_factory_finalize (GObject *object) +{ + SysprofChartLayerFactory *self = (SysprofChartLayerFactory *)object; + + g_clear_object (&self->scope); + g_clear_pointer (&self->resource, g_free); + g_clear_pointer (&self->bytes, g_bytes_unref); + + G_OBJECT_CLASS (sysprof_chart_layer_factory_parent_class)->finalize (object); +} + +static void +sysprof_chart_layer_factory_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayerFactory *self = SYSPROF_CHART_LAYER_FACTORY (object); + + switch (prop_id) + { + case PROP_SCOPE: + g_value_set_object (value, self->scope); + break; + + case PROP_RESOURCE: + g_value_set_string (value, self->resource); + break; + + case PROP_BYTES: + g_value_set_boxed (value, self->bytes); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_factory_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayerFactory *self = SYSPROF_CHART_LAYER_FACTORY (object); + + switch (prop_id) + { + case PROP_BYTES: + self->bytes = g_value_dup_boxed (value); + break; + + case PROP_RESOURCE: + self->resource = g_value_dup_string (value); + break; + + case PROP_SCOPE: + self->scope = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_factory_class_init (SysprofChartLayerFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = sysprof_chart_layer_factory_constructed; + object_class->finalize = sysprof_chart_layer_factory_finalize; + object_class->get_property = sysprof_chart_layer_factory_get_property; + object_class->set_property = sysprof_chart_layer_factory_set_property; + + properties[PROP_SCOPE] = + g_param_spec_object ("scope", NULL, NULL, + GTK_TYPE_BUILDER_SCOPE, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_RESOURCE] = + g_param_spec_string ("resource", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_BYTES] = + g_param_spec_boxed ("bytes", NULL, NULL, + G_TYPE_BYTES, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_chart_layer_factory_init (SysprofChartLayerFactory *self) +{ +} + +SysprofChartLayerFactory * +sysprof_chart_layer_factory_new_from_bytes (GtkBuilderScope *scope, + GBytes *bytes) +{ + g_return_val_if_fail (!scope || GTK_IS_BUILDER_SCOPE (scope), NULL); + g_return_val_if_fail (bytes != NULL, NULL); + + return g_object_new (SYSPROF_TYPE_CHART_LAYER_FACTORY, + "scope", scope, + "bytes", bytes, + NULL); +} + +SysprofChartLayerFactory * +sysprof_chart_layer_factory_new_from_resource (GtkBuilderScope *scope, + const char *resource) +{ + g_return_val_if_fail (!scope || GTK_IS_BUILDER_SCOPE (scope), NULL); + g_return_val_if_fail (resource != NULL, NULL); + + return g_object_new (SYSPROF_TYPE_CHART_LAYER_FACTORY, + "scope", scope, + "resource", resource, + NULL); +} diff --git a/src/libsysprof/sysprof-private.h b/src/sysprof/sysprof-chart-layer-factory.h similarity index 55% rename from src/libsysprof/sysprof-private.h rename to src/sysprof/sysprof-chart-layer-factory.h index d9cd7733..88c9e64b 100644 --- a/src/libsysprof/sysprof-private.h +++ b/src/sysprof/sysprof-chart-layer-factory.h @@ -1,6 +1,6 @@ -/* sysprof-private.h +/* sysprof-chart-layer-factory.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,20 +20,17 @@ #pragma once -#include - -#include "sysprof.h" -#include "sysprof-kallsyms.h" +#include "sysprof-chart-layer.h" G_BEGIN_DECLS -typedef GArray SysprofKernelSymbols; +#define SYSPROF_TYPE_CHART_LAYER_FACTORY (sysprof_chart_layer_factory_get_type()) -SysprofKernelSymbols *_sysprof_kernel_symbols_get_shared (void); -SysprofKernelSymbols *_sysprof_kernel_symbols_new_from_kallsyms (SysprofKallsyms *kallsyms); -const SysprofKernelSymbol *_sysprof_kernel_symbols_lookup (const SysprofKernelSymbols *self, - SysprofCaptureAddress address); +G_DECLARE_FINAL_TYPE (SysprofChartLayerFactory, sysprof_chart_layer_factory, SYSPROF, CHART_LAYER_FACTORY, GObject) -G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofKernelSymbols, g_array_unref) +SysprofChartLayerFactory *sysprof_chart_layer_factory_new_from_resource (GtkBuilderScope *scope, + const char *resource); +SysprofChartLayerFactory *sysprof_chart_layer_factory_new_from_bytes (GtkBuilderScope *scope, + GBytes *bytes); G_END_DECLS diff --git a/src/sysprof/sysprof-chart-layer-item-widget.c b/src/sysprof/sysprof-chart-layer-item-widget.c new file mode 100644 index 00000000..f2fee192 --- /dev/null +++ b/src/sysprof/sysprof-chart-layer-item-widget.c @@ -0,0 +1,210 @@ +/* sysprof-chart-layer-item-widget.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart-layer-item-widget.h" + +struct _SysprofChartLayerItemWidget +{ + SysprofChartLayer parent_instance; + SysprofChartLayerItem *item; + guint disposed : 1; +}; + +enum { + PROP_0, + PROP_ITEM, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofChartLayerItemWidget, sysprof_chart_layer_item_widget, SYSPROF_TYPE_CHART_LAYER) + +static GParamSpec *properties [N_PROPS]; + +static SysprofChartLayer * +get_layer (SysprofChartLayer *self) +{ + GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + + return SYSPROF_CHART_LAYER (child); +} + + +static gpointer +sysprof_chart_layer_item_widget_lookup_item (SysprofChartLayer *self, + double x, + double y) +{ + SysprofChartLayer *layer; + + if (!(layer = get_layer (self))) + return NULL; + + return sysprof_chart_layer_lookup_item (layer, x, y); +} + +static void +sysprof_chart_layer_item_widget_snapshot_motion (SysprofChartLayer *self, + GtkSnapshot *snapshot, + double x, + double y) +{ + SysprofChartLayer *layer; + + if (!(layer = get_layer (self))) + return; + + sysprof_chart_layer_snapshot_motion (layer, snapshot, x, y); +} + +static void +sysprof_chart_layer_item_widget_notify_layer_cb (SysprofChartLayerItemWidget *self, + GParamSpec *pspec, + SysprofChartLayerItem *item) +{ + GtkWidget *child; + + g_assert (SYSPROF_IS_CHART_LAYER_ITEM_WIDGET (self)); + g_assert (SYSPROF_IS_CHART_LAYER_ITEM (item)); + + if (self->disposed) + return; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + if ((child = GTK_WIDGET (sysprof_chart_layer_item_get_layer (item)))) + gtk_widget_set_parent (child, GTK_WIDGET (self)); +} + +static void +sysprof_chart_layer_item_widget_constructed (GObject *object) +{ + SysprofChartLayerItemWidget *self = (SysprofChartLayerItemWidget *)object; + SysprofChartLayer *layer; + + G_OBJECT_CLASS (sysprof_chart_layer_item_widget_parent_class)->constructed (object); + + g_return_if_fail (self->item != NULL); + + if ((layer = sysprof_chart_layer_item_get_layer (self->item))) + gtk_widget_set_parent (GTK_WIDGET (layer), GTK_WIDGET (self)); + + g_signal_connect_object (self->item, + "notify::layer", + G_CALLBACK (sysprof_chart_layer_item_widget_notify_layer_cb), + self, + G_CONNECT_SWAPPED); +} + +static void +sysprof_chart_layer_item_widget_dispose (GObject *object) +{ + SysprofChartLayerItemWidget *self = (SysprofChartLayerItemWidget *)object; + GtkWidget *child; + + self->disposed = TRUE; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&self->item); + + G_OBJECT_CLASS (sysprof_chart_layer_item_widget_parent_class)->dispose (object); +} + +static void +sysprof_chart_layer_item_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayerItemWidget *self = SYSPROF_CHART_LAYER_ITEM_WIDGET (object); + + switch (prop_id) + { + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_item_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayerItemWidget *self = SYSPROF_CHART_LAYER_ITEM_WIDGET (object); + + switch (prop_id) + { + case PROP_ITEM: + self->item = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_item_widget_class_init (SysprofChartLayerItemWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + SysprofChartLayerClass *chart_layer_class = SYSPROF_CHART_LAYER_CLASS (klass); + + object_class->constructed = sysprof_chart_layer_item_widget_constructed; + object_class->dispose = sysprof_chart_layer_item_widget_dispose; + object_class->get_property = sysprof_chart_layer_item_widget_get_property; + object_class->set_property = sysprof_chart_layer_item_widget_set_property; + + chart_layer_class->lookup_item = sysprof_chart_layer_item_widget_lookup_item; + chart_layer_class->snapshot_motion = sysprof_chart_layer_item_widget_snapshot_motion; + + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER_ITEM, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +sysprof_chart_layer_item_widget_init (SysprofChartLayerItemWidget *self) +{ +} + +SysprofChartLayerItemWidget * +sysprof_chart_layer_item_widget_new (SysprofChartLayerItem *item) +{ + g_return_val_if_fail (SYSPROF_IS_CHART_LAYER_ITEM (item), NULL); + + return g_object_new (SYSPROF_TYPE_CHART_LAYER_ITEM_WIDGET, + "item", item, + NULL); +} diff --git a/src/sysprof/sysprof-chart-layer-item-widget.h b/src/sysprof/sysprof-chart-layer-item-widget.h new file mode 100644 index 00000000..3a30eca0 --- /dev/null +++ b/src/sysprof/sysprof-chart-layer-item-widget.h @@ -0,0 +1,34 @@ +/* sysprof-chart-layer-item-widget.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-chart-layer.h" +#include "sysprof-chart-layer-item.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CHART_LAYER_ITEM_WIDGET (sysprof_chart_layer_item_widget_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofChartLayerItemWidget, sysprof_chart_layer_item_widget, SYSPROF, CHART_LAYER_ITEM_WIDGET, SysprofChartLayer) + +SysprofChartLayerItemWidget *sysprof_chart_layer_item_widget_new (SysprofChartLayerItem *item); + +G_END_DECLS diff --git a/src/sysprof/sysprof-chart-layer-item.c b/src/sysprof/sysprof-chart-layer-item.c new file mode 100644 index 00000000..fb46b590 --- /dev/null +++ b/src/sysprof/sysprof-chart-layer-item.c @@ -0,0 +1,152 @@ +/* sysprof-chart-layer-item.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart-layer-item.h" + +struct _SysprofChartLayerItem +{ + GObject parent_instance; + GObject *item; + SysprofChartLayer *layer; +}; + +enum { + PROP_0, + PROP_ITEM, + PROP_LAYER, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofChartLayerItem, sysprof_chart_layer_item, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_chart_layer_item_dispose (GObject *object) +{ + SysprofChartLayerItem *self = (SysprofChartLayerItem *)object; + + g_clear_object (&self->layer); + g_clear_object (&self->item); + + G_OBJECT_CLASS (sysprof_chart_layer_item_parent_class)->dispose (object); +} + +static void +sysprof_chart_layer_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayerItem *self = SYSPROF_CHART_LAYER_ITEM (object); + + switch (prop_id) + { + case PROP_LAYER: + g_value_set_object (value, sysprof_chart_layer_item_get_layer (self)); + break; + + case PROP_ITEM: + g_value_set_object (value, sysprof_chart_layer_item_get_item (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_item_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayerItem *self = SYSPROF_CHART_LAYER_ITEM (object); + + switch (prop_id) + { + case PROP_LAYER: + sysprof_chart_layer_item_set_layer (self, g_value_get_object (value)); + break; + + case PROP_ITEM: + self->item = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_item_class_init (SysprofChartLayerItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_chart_layer_item_dispose; + object_class->get_property = sysprof_chart_layer_item_get_property; + object_class->set_property = sysprof_chart_layer_item_set_property; + + properties[PROP_LAYER] = + g_param_spec_object ("layer", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_chart_layer_item_init (SysprofChartLayerItem *self) +{ +} + +SysprofChartLayer * +sysprof_chart_layer_item_get_layer (SysprofChartLayerItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_CHART_LAYER_ITEM (self), NULL); + + return self->layer; +} + +void +sysprof_chart_layer_item_set_layer (SysprofChartLayerItem *self, + SysprofChartLayer *layer) +{ + g_return_if_fail (SYSPROF_IS_CHART_LAYER_ITEM (self)); + g_return_if_fail (!layer || SYSPROF_IS_CHART_LAYER (layer)); + + if (g_set_object (&self->layer, layer)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LAYER]); +} + +gpointer +sysprof_chart_layer_item_get_item (SysprofChartLayerItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_CHART_LAYER_ITEM (self), NULL); + + return self->item; +} diff --git a/src/sysprof/sysprof-chart-layer-item.h b/src/sysprof/sysprof-chart-layer-item.h new file mode 100644 index 00000000..d0200ae2 --- /dev/null +++ b/src/sysprof/sysprof-chart-layer-item.h @@ -0,0 +1,36 @@ +/* sysprof-chart-layer-item.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-chart-layer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CHART_LAYER_ITEM (sysprof_chart_layer_item_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofChartLayerItem, sysprof_chart_layer_item, SYSPROF, CHART_LAYER_ITEM, GObject) + +gpointer sysprof_chart_layer_item_get_item (SysprofChartLayerItem *self); +SysprofChartLayer *sysprof_chart_layer_item_get_layer (SysprofChartLayerItem *self); +void sysprof_chart_layer_item_set_layer (SysprofChartLayerItem *self, + SysprofChartLayer *layer); + +G_END_DECLS diff --git a/src/sysprof/sysprof-chart-layer-private.h b/src/sysprof/sysprof-chart-layer-private.h new file mode 100644 index 00000000..9cf73744 --- /dev/null +++ b/src/sysprof/sysprof-chart-layer-private.h @@ -0,0 +1,30 @@ +/* sysprof-chart-layer-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-chart-layer.h" + +G_BEGIN_DECLS + +const GdkRGBA *_sysprof_chart_layer_get_accent_bg_color (void); +const GdkRGBA *_sysprof_chart_layer_get_accent_fg_color (void); + +G_END_DECLS diff --git a/src/sysprof/sysprof-chart-layer.c b/src/sysprof/sysprof-chart-layer.c new file mode 100644 index 00000000..9e54ec5f --- /dev/null +++ b/src/sysprof/sysprof-chart-layer.c @@ -0,0 +1,225 @@ +/* sysprof-chart-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart.h" +#include "sysprof-chart-layer-private.h" + +typedef struct +{ + char *title; +} SysprofChartLayerPrivate; + +enum { + PROP_0, + PROP_CHART, + PROP_TITLE, + N_PROPS +}; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SysprofChartLayer, sysprof_chart_layer, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; +static GdkRGBA accent_fg_color; +static GdkRGBA accent_bg_color; + +static void +sysprof_chart_layer_css_changed (GtkWidget *widget, + GtkCssStyleChange *change) +{ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + GtkStyleContext *style_context; + + GTK_WIDGET_CLASS (sysprof_chart_layer_parent_class)->css_changed (widget, change); + + style_context = gtk_widget_get_style_context (widget); + + gtk_style_context_lookup_color (style_context, "accent_fg_color", &accent_fg_color); + gtk_style_context_lookup_color (style_context, "accent_bg_color", &accent_bg_color); +G_GNUC_END_IGNORE_DEPRECATIONS +} + +static void +sysprof_chart_layer_dispose (GObject *object) +{ + SysprofChartLayer *self = (SysprofChartLayer *)object; + SysprofChartLayerPrivate *priv = sysprof_chart_layer_get_instance_private (self); + + g_clear_pointer (&priv->title, g_free); + + G_OBJECT_CLASS (sysprof_chart_layer_parent_class)->dispose (object); +} + +static void +sysprof_chart_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayer *self = SYSPROF_CHART_LAYER (object); + + switch (prop_id) + { + case PROP_CHART: + g_value_set_object (value, gtk_widget_get_ancestor (GTK_WIDGET (self), SYSPROF_TYPE_CHART)); + break; + + case PROP_TITLE: + g_value_set_string (value, sysprof_chart_layer_get_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofChartLayer *self = SYSPROF_CHART_LAYER (object); + + switch (prop_id) + { + case PROP_TITLE: + sysprof_chart_layer_set_title (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_layer_root (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (sysprof_chart_layer_parent_class)->root (widget); + + g_object_notify_by_pspec (G_OBJECT (widget), properties[PROP_CHART]); +} + +static void +sysprof_chart_layer_class_init (SysprofChartLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_chart_layer_dispose; + object_class->get_property = sysprof_chart_layer_get_property; + object_class->set_property = sysprof_chart_layer_set_property; + + widget_class->css_changed = sysprof_chart_layer_css_changed; + widget_class->root = sysprof_chart_layer_root; + + properties[PROP_CHART] = + g_param_spec_object ("chart", NULL, NULL, + SYSPROF_TYPE_CHART, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "layer"); +} + +static void +sysprof_chart_layer_init (SysprofChartLayer *self) +{ +} + +const char * +sysprof_chart_layer_get_title (SysprofChartLayer *self) +{ + SysprofChartLayerPrivate *priv = sysprof_chart_layer_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_CHART_LAYER (self), NULL); + + return priv->title; +} + +void +sysprof_chart_layer_set_title (SysprofChartLayer *self, + const char *title) +{ + SysprofChartLayerPrivate *priv = sysprof_chart_layer_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_CHART_LAYER (self)); + + if (g_set_str (&priv->title, title)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +void +sysprof_chart_layer_snapshot_motion (SysprofChartLayer *self, + GtkSnapshot *snapshot, + double x, + double y) +{ + g_return_if_fail (SYSPROF_IS_CHART_LAYER (self)); + g_return_if_fail (GTK_IS_SNAPSHOT (snapshot)); + + if (SYSPROF_CHART_LAYER_GET_CLASS (self)->snapshot_motion) + SYSPROF_CHART_LAYER_GET_CLASS (self)->snapshot_motion (self, snapshot, x, y); +} + +/** + * sysprof_chart_layer_lookup_item: + * @self: a #SysprofChartLayer + * @x: the coordinate on the X axis + * @y: the coordinate on the Y axis + * + * Locates an item at `(x,y)`. + * + * If no item is found at `(x,y)`, then %NULL is returned. + * + * Returns: (transfer full) (nullable) (type GObject): A #GObject + * if successful; otherwise %NULL. + */ +gpointer +sysprof_chart_layer_lookup_item (SysprofChartLayer *self, + double x, + double y) +{ + g_return_val_if_fail (SYSPROF_IS_CHART_LAYER (self), NULL); + + if (SYSPROF_CHART_LAYER_GET_CLASS (self)->lookup_item) + return SYSPROF_CHART_LAYER_GET_CLASS (self)->lookup_item (self, x, y); + + return NULL; +} + +const GdkRGBA * +_sysprof_chart_layer_get_accent_bg_color (void) +{ + return &accent_bg_color; +} + +const GdkRGBA * +_sysprof_chart_layer_get_accent_fg_color (void) +{ + return &accent_fg_color; +} diff --git a/src/sysprof/sysprof-chart-layer.h b/src/sysprof/sysprof-chart-layer.h new file mode 100644 index 00000000..0eb4427f --- /dev/null +++ b/src/sysprof/sysprof-chart-layer.h @@ -0,0 +1,57 @@ +/* sysprof-chart-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CHART_LAYER (sysprof_chart_layer_get_type()) + +G_DECLARE_DERIVABLE_TYPE (SysprofChartLayer, sysprof_chart_layer, SYSPROF, CHART_LAYER, GtkWidget) + +struct _SysprofChartLayerClass +{ + GtkWidgetClass parent_class; + + gpointer (*lookup_item) (SysprofChartLayer *self, + double x, + double y); + void (*snapshot_motion) (SysprofChartLayer *self, + GtkSnapshot *snapshot, + double x, + double y); +}; + +const char *sysprof_chart_layer_get_title (SysprofChartLayer *self); +void sysprof_chart_layer_set_title (SysprofChartLayer *self, + const char *title); +gpointer sysprof_chart_layer_lookup_item (SysprofChartLayer *self, + double x, + double y); +void sysprof_chart_layer_snapshot_motion (SysprofChartLayer *self, + GtkSnapshot *snapshot, + double x, + double y); + +G_END_DECLS diff --git a/src/sysprof/sysprof-chart.c b/src/sysprof/sysprof-chart.c new file mode 100644 index 00000000..ea2ba09d --- /dev/null +++ b/src/sysprof/sysprof-chart.c @@ -0,0 +1,577 @@ +/* sysprof-chart.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-css-private.h" +#include "sysprof-chart.h" +#include "sysprof-chart-layer-factory-private.h" +#include "sysprof-chart-layer-item.h" +#include "sysprof-chart-layer-item-widget.h" + +typedef struct +{ + SysprofSession *session; + char *title; + + SysprofChartLayerFactory *factory; + GListModel *model; + + double motion_x; + double motion_y; + + guint pointer_in_chart : 1; +} SysprofChartPrivate; + +enum { + PROP_0, + PROP_FACTORY, + PROP_MODEL, + PROP_SESSION, + PROP_TITLE, + N_PROPS +}; + +enum { + ACTIVATE_LAYER_ITEM, + N_SIGNALS +}; + +G_DEFINE_TYPE_WITH_PRIVATE (SysprofChart, sysprof_chart, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS]; +static guint signals[N_SIGNALS]; + + +static void +sysprof_chart_motion_enter_cb (SysprofChart *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_assert (SYSPROF_IS_CHART (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + priv->motion_x = x; + priv->motion_y = y; + priv->pointer_in_chart = TRUE; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_chart_motion_cb (SysprofChart *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_assert (SYSPROF_IS_CHART (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + priv->motion_x = x; + priv->motion_y = y; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_chart_motion_leave_cb (SysprofChart *self, + GtkEventControllerMotion *motion) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_assert (SYSPROF_IS_CHART (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + priv->motion_x = -1; + priv->motion_y = -1; + priv->pointer_in_chart = FALSE; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_chart_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + g_assert (SYSPROF_IS_CHART (widget)); + + GTK_WIDGET_CLASS (sysprof_chart_parent_class)->size_allocate (widget, width, height, baseline); + + for (GtkWidget *child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + gtk_widget_size_allocate (child, + &(GtkAllocation) {0, 0, width, height}, + baseline); +} + +static void +sysprof_chart_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofChart *self = (SysprofChart *)widget; + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_assert (SYSPROF_IS_CHART (self)); + + gtk_snapshot_push_clip (snapshot, + &GRAPHENE_RECT_INIT (0, 0, + gtk_widget_get_width (widget), + gtk_widget_get_height (widget))); + + GTK_WIDGET_CLASS (sysprof_chart_parent_class)->snapshot (widget, snapshot); + + if (priv->pointer_in_chart) + { + GtkWidget *pick = gtk_widget_pick (widget, priv->motion_x, priv->motion_y, GTK_PICK_DEFAULT); + + if (SYSPROF_IS_CHART_LAYER (pick)) + sysprof_chart_layer_snapshot_motion (SYSPROF_CHART_LAYER (pick), + snapshot, + priv->motion_x, + priv->motion_y); + } + + gtk_snapshot_pop (snapshot); +} + +static gboolean +sysprof_chart_click_pressed_cb (SysprofChart *self, + int n_presses, + double x, + double y, + GtkGestureClick *click) +{ + g_autoptr(GObject) item = NULL; + GtkWidget *pick; + gboolean ret = GDK_EVENT_PROPAGATE; + + g_assert (SYSPROF_IS_CHART (self)); + g_assert (GTK_IS_GESTURE_CLICK (click)); + + if (n_presses != 1) + return GDK_EVENT_PROPAGATE; + + if ((pick = gtk_widget_pick (GTK_WIDGET (self), x, y, GTK_PICK_DEFAULT)) && + SYSPROF_IS_CHART_LAYER (pick) && + (item = sysprof_chart_layer_lookup_item (SYSPROF_CHART_LAYER (pick), x, y))) + g_signal_emit (self, signals[ACTIVATE_LAYER_ITEM], 0, pick, item, &ret); + + return ret; +} + +static void +sysprof_chart_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minium_baseline, + int *natural_baseline) +{ + *minium_baseline = -1; + *natural_baseline = -1; + + for (GtkWidget *child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + int child_min; + int child_nat; + + gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL); + + *minimum = MAX (*minimum, child_min); + *natural = MAX (*natural, child_nat); + } +} + +static void +sysprof_chart_dispose (GObject *object) +{ + SysprofChart *self = (SysprofChart *)object; + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + GtkWidget *child; + + sysprof_chart_set_model (self, NULL); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&priv->session); + g_clear_pointer (&priv->title, g_free); + + G_OBJECT_CLASS (sysprof_chart_parent_class)->dispose (object); +} + +static void +sysprof_chart_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofChart *self = SYSPROF_CHART (object); + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + switch (prop_id) + { + case PROP_FACTORY: + g_value_set_object (value, priv->factory); + break; + + case PROP_MODEL: + g_value_set_object (value, sysprof_chart_get_model (self)); + break; + + case PROP_SESSION: + g_value_set_object (value, sysprof_chart_get_session (self)); + break; + + case PROP_TITLE: + g_value_set_string (value, sysprof_chart_get_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofChart *self = SYSPROF_CHART (object); + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + switch (prop_id) + { + case PROP_FACTORY: + priv->factory = g_value_dup_object (value); + break; + + case PROP_MODEL: + sysprof_chart_set_model (self, g_value_get_object (value)); + break; + + case PROP_SESSION: + sysprof_chart_set_session (self, g_value_get_object (value)); + break; + + case PROP_TITLE: + sysprof_chart_set_title (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_chart_class_init (SysprofChartClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_chart_dispose; + object_class->get_property = sysprof_chart_get_property; + object_class->set_property = sysprof_chart_set_property; + + widget_class->measure = sysprof_chart_measure; + widget_class->size_allocate = sysprof_chart_size_allocate; + widget_class->snapshot = sysprof_chart_snapshot; + + properties [PROP_MODEL] = + g_param_spec_object ("model", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_FACTORY] = + g_param_spec_object ("factory", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER_FACTORY, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + signals[ACTIVATE_LAYER_ITEM] = + g_signal_new ("activate-layer-item", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (SysprofChartClass, activate_layer_item), + g_signal_accumulator_true_handled, NULL, + NULL, + G_TYPE_BOOLEAN, + 2, + SYSPROF_TYPE_CHART_LAYER, + G_TYPE_OBJECT); + + gtk_widget_class_set_css_name (widget_class, "chart"); + + g_type_ensure (SYSPROF_TYPE_CHART_LAYER_FACTORY); + g_type_ensure (SYSPROF_TYPE_CHART_LAYER_ITEM); + g_type_ensure (SYSPROF_TYPE_CHART_LAYER_ITEM_WIDGET); +} + +static void +sysprof_chart_init (SysprofChart *self) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + GtkEventController *motion; + GtkEventController *click; + + priv->motion_x = -1; + priv->motion_y = -1; + + _sysprof_css_init (); + + motion = gtk_event_controller_motion_new (); + g_signal_connect_object (motion, + "enter", + G_CALLBACK (sysprof_chart_motion_enter_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (motion, + "leave", + G_CALLBACK (sysprof_chart_motion_leave_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (motion, + "motion", + G_CALLBACK (sysprof_chart_motion_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_add_controller (GTK_WIDGET (self), g_steal_pointer (&motion)); + + click = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + g_signal_connect_object (click, + "pressed", + G_CALLBACK (sysprof_chart_click_pressed_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_add_controller (GTK_WIDGET (self), g_steal_pointer (&click)); +} + +const char * +sysprof_chart_get_title (SysprofChart *self) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_CHART (self), NULL); + + return priv->title; +} + +/** + * sysprof_chart_get_session: + * @self: a #SysprofChart + * + * Gets the #SysprofSession of the chart. + * + * Returns: (transfer none) (nullable): a #SysprofSession or %NULL + */ +SysprofSession * +sysprof_chart_get_session (SysprofChart *self) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_CHART (self), NULL); + + return priv->session; +} + +/** + * sysprof_chart_set_session: + * @self: a #SysprofChart + * @session: (nullable): a #SysprofSession + * + * Sets the session for the chart. + */ +void +sysprof_chart_set_session (SysprofChart *self, + SysprofSession *session) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_CHART (self)); + g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); + + if (g_set_object (&priv->session, session)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); +} + +void +sysprof_chart_set_title (SysprofChart *self, + const char *title) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_CHART (self)); + + if (g_set_str (&priv->title, title)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +void +sysprof_chart_add_layer (SysprofChart *self, + SysprofChartLayer *layer) +{ + g_return_if_fail (SYSPROF_IS_CHART (self)); + g_return_if_fail (SYSPROF_IS_CHART_LAYER (layer)); + g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (layer)) == NULL); + + gtk_widget_set_parent (GTK_WIDGET (layer), GTK_WIDGET (self)); +} + +void +sysprof_chart_remove_layer (SysprofChart *self, + SysprofChartLayer *layer) +{ + g_return_if_fail (SYSPROF_IS_CHART (self)); + g_return_if_fail (SYSPROF_IS_CHART_LAYER (layer)); + g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (layer)) == GTK_WIDGET (self)); + + gtk_widget_unparent (GTK_WIDGET (layer)); +} + + +GListModel * +sysprof_chart_get_model (SysprofChart *self) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_CHART (self), NULL); + + return priv->model; +} + +static void +sysprof_chart_items_changed_cb (SysprofChart *self, + guint position, + guint removed, + guint added, + GListModel *model) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_assert (SYSPROF_IS_CHART (self)); + g_assert (G_IS_LIST_MODEL (model)); + + if (removed > 0) + { + GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + + for (guint i = 1; i <= position; i++) + child = gtk_widget_get_next_sibling (child); + + for (guint i = 0; i < removed; i++) + { + GtkWidget *target = child; + child = gtk_widget_get_next_sibling (child); + gtk_widget_unparent (target); + } + } + + if (added > 0) + { + GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self)); + + for (guint i = 1; i < position; i++) + child = gtk_widget_get_next_sibling (child); + + for (guint i = 0; i < added; i++) + { + g_autoptr(GObject) item = g_list_model_get_item (model, position + i); + g_autoptr(SysprofChartLayerItem) layer_item = NULL; + SysprofChartLayerItemWidget *widget; + + layer_item = g_object_new (SYSPROF_TYPE_CHART_LAYER_ITEM, + "item", item, + NULL); + if (priv->factory) + _sysprof_chart_layer_factory_setup (priv->factory, layer_item); + widget = sysprof_chart_layer_item_widget_new (layer_item); + gtk_widget_insert_after (GTK_WIDGET (widget), GTK_WIDGET (self), child); + child = GTK_WIDGET (widget); + } + } +} + +void +sysprof_chart_set_model (SysprofChart *self, + GListModel *model) +{ + SysprofChartPrivate *priv = sysprof_chart_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_CHART (self)); + + if (priv->model == model) + return; + + if (model) + g_object_ref (model); + + if (priv->model) + { + GtkWidget *child; + + g_signal_handlers_disconnect_by_func (priv->model, + G_CALLBACK (sysprof_chart_items_changed_cb), + self); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&priv->model); + } + + priv->model = model; + + if (model) + { + guint n_items = g_list_model_get_n_items (model); + g_signal_connect_object (priv->model, + "items-changed", + G_CALLBACK (sysprof_chart_items_changed_cb), + self, + G_CONNECT_SWAPPED); + if (n_items) + sysprof_chart_items_changed_cb (self, 0, 0, n_items, model); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODEL]); +} diff --git a/src/sysprof/sysprof-chart.h b/src/sysprof/sysprof-chart.h new file mode 100644 index 00000000..33dc1f18 --- /dev/null +++ b/src/sysprof/sysprof-chart.h @@ -0,0 +1,60 @@ +/* sysprof-chart.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-chart-layer.h" +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_CHART (sysprof_chart_get_type()) + +G_DECLARE_DERIVABLE_TYPE (SysprofChart, sysprof_chart, SYSPROF, CHART, GtkWidget) + +struct _SysprofChartClass +{ + GtkWidgetClass parent_class; + + gboolean (*activate_layer_item) (SysprofChart *self, + SysprofChartLayer *layer, + gpointer item); +}; + +GtkWidget *sysprof_chart_new (void); +SysprofSession *sysprof_chart_get_session (SysprofChart *self); +void sysprof_chart_set_session (SysprofChart *self, + SysprofSession *session); +const char *sysprof_chart_get_title (SysprofChart *self); +void sysprof_chart_set_title (SysprofChart *self, + const char *title); +void sysprof_chart_add_layer (SysprofChart *self, + SysprofChartLayer *layer); +void sysprof_chart_remove_layer (SysprofChart *self, + SysprofChartLayer *layer); +GListModel *sysprof_chart_get_model (SysprofChart *self); +void sysprof_chart_set_model (SysprofChart *self, + GListModel *model); + +G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-visualizer-group-header.h b/src/sysprof/sysprof-color-iter-private.h similarity index 70% rename from src/libsysprof-ui/sysprof-visualizer-group-header.h rename to src/sysprof/sysprof-color-iter-private.h index 2a1e9c82..0caae67e 100644 --- a/src/libsysprof-ui/sysprof-visualizer-group-header.h +++ b/src/sysprof/sysprof-color-iter-private.h @@ -1,6 +1,6 @@ -/* sysprof-visualizer-group-header.h +/* sysprof-color-iter-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,8 +24,14 @@ G_BEGIN_DECLS -#define SYSPROF_TYPE_VISUALIZER_GROUP_HEADER (sysprof_visualizer_group_header_get_type()) +typedef struct _SysprofColorIter +{ + GdkRGBA *colors; + guint n_colors; + guint position; +} SysprofColorIter; -G_DECLARE_FINAL_TYPE (SysprofVisualizerGroupHeader, sysprof_visualizer_group_header, SYSPROF, VISUALIZER_GROUP_HEADER, GtkListBoxRow) +void sysprof_color_iter_init (SysprofColorIter *iter); +const GdkRGBA *sysprof_color_iter_next (SysprofColorIter *iter); G_END_DECLS diff --git a/src/sysprof/sysprof-color-iter.c b/src/sysprof/sysprof-color-iter.c new file mode 100644 index 00000000..051184a1 --- /dev/null +++ b/src/sysprof/sysprof-color-iter.c @@ -0,0 +1,78 @@ +/* sysprof-color-iter.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-color-iter-private.h" + +static GdkRGBA *colors; +static guint n_colors; + +void +sysprof_color_iter_init (SysprofColorIter *iter) +{ + if (g_once_init_enter (&colors)) + { + static const char *color_strings[] = { + "#3584e4", + "#ff7800", + "#33d17a", + "#9141ac", + "#e5a50a", + "#63452c", + "#ffbe6f" + "#613583", + "#9a9996", + "#ed333b", + "#1a5fb4", + "#26a269", + "#c061cb", + "#c64600", + }; + GdkRGBA *_colors; + + n_colors = G_N_ELEMENTS (color_strings); + _colors = g_new0 (GdkRGBA, n_colors); + + for (guint i = 0; i < n_colors; i++) + gdk_rgba_parse (&_colors[i], color_strings[i]); + + g_once_init_leave (&colors, _colors); + } + + iter->colors = colors; + iter->n_colors = n_colors; + iter->position = 0; +} + +const GdkRGBA * +sysprof_color_iter_next (SysprofColorIter *iter) +{ + const GdkRGBA *ret; + + g_assert (iter->colors != NULL); + g_assert (iter->n_colors > 0); + + ret = &iter->colors[iter->position % iter->n_colors]; + + iter->position++; + + return ret; +} diff --git a/src/sysprof/sysprof-column-layer.c b/src/sysprof/sysprof-column-layer.c new file mode 100644 index 00000000..ddff1f9c --- /dev/null +++ b/src/sysprof/sysprof-column-layer.c @@ -0,0 +1,373 @@ +/* sysprof-column-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-axis.h" +#include "sysprof-chart-layer-private.h" +#include "sysprof-column-layer.h" +#include "sysprof-normalized-series.h" +#include "sysprof-xy-layer-private.h" + +struct _SysprofColumnLayer +{ + SysprofXYLayer parent_instance; + + GdkRGBA color; + GdkRGBA hover_color; + + guint color_set : 1; + guint hover_color_set : 1; +}; + +enum { + PROP_0, + PROP_COLOR, + PROP_HOVER_COLOR, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofColumnLayer, sysprof_column_layer, SYSPROF_TYPE_XY_LAYER) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_column_layer_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofColumnLayer *self = (SysprofColumnLayer *)widget; + const double *x_values; + const double *y_values; + const GdkRGBA *color; + double last_x = 0; + double last_y = 0; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_COLUMN_LAYER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + _sysprof_xy_layer_get_xy (SYSPROF_XY_LAYER (self), &x_values, &y_values, &n_values); + + if (width == 0 || height == 0 || n_values == 0) + return; + + if (self->color_set) + color = &self->color; + else + color = _sysprof_chart_layer_get_accent_bg_color (); + + if (color->alpha == .0) + return; + + gtk_snapshot_save (snapshot); + + if (!sysprof_xy_layer_get_flip_y (SYSPROF_XY_LAYER (self))) + { + graphene_matrix_t flip_y; + + graphene_matrix_init_from_2d (&flip_y, 1, 0, 0, -1, 0, height); + gtk_snapshot_transform_matrix (snapshot, &flip_y); + } + + for (guint i = 0; i < n_values; i++) + { + double x; + double y; + + if (x_values[i] < .0) + continue; + + if (x_values[i] > 1.) + break; + + x = round (x_values[i] * width); + y = ceil (y_values[i] * height); + + if (x == last_x && y < last_y) + continue; + + gtk_snapshot_append_color (snapshot, + color, + &GRAPHENE_RECT_INIT (x, 0, 1, y)); + + last_x = x; + last_y = y; + } + + gtk_snapshot_restore (snapshot); +} + +static guint +sysprof_column_layer_get_index_at_coord (SysprofColumnLayer *self, + double x, + double y, + graphene_rect_t *area) +{ + graphene_point_t point; + const double *x_values; + const double *y_values; + gboolean flip_y; + guint best = GTK_INVALID_LIST_POSITION; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_COLUMN_LAYER (self)); + + width = gtk_widget_get_width (GTK_WIDGET (self)); + height = gtk_widget_get_height (GTK_WIDGET (self)); + flip_y = sysprof_xy_layer_get_flip_y (SYSPROF_XY_LAYER (self)); + + _sysprof_xy_layer_get_xy (SYSPROF_XY_LAYER (self), &x_values, &y_values, &n_values); + + if (width == 0 || height == 0 || n_values == 0) + return GTK_INVALID_LIST_POSITION; + + point = GRAPHENE_POINT_INIT (x, y); + + for (guint i = 0; i < n_values; i++) + { + graphene_rect_t rect; + + if (flip_y) + rect = GRAPHENE_RECT_INIT (x_values[i]*width, + 0, + 1, + y_values[i]*height); + else + rect = GRAPHENE_RECT_INIT (x_values[i]*width, + height - y_values[i]*height, + 1, + y_values[i]*height); + + if (graphene_rect_contains_point (&rect, &point)) + { + if (area != NULL) + *area = rect; + + return i; + } + + if (rect.origin.x <= x && + rect.origin.x + rect.size.width >= x) + { + if (area != NULL) + *area = rect; + + best = i; + } + } + + return best; +} + +static void +sysprof_column_layer_snapshot_motion (SysprofChartLayer *layer, + GtkSnapshot *snapshot, + double x, + double y) +{ + SysprofColumnLayer *self = (SysprofColumnLayer *)layer; + graphene_rect_t rect; + guint position; + + g_assert (SYSPROF_IS_COLUMN_LAYER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + position = sysprof_column_layer_get_index_at_coord (self, x, y, &rect); + + if (position != GTK_INVALID_LIST_POSITION) + gtk_snapshot_append_color (snapshot, &self->hover_color, &rect); +} + +static gpointer +sysprof_column_layer_lookup_item (SysprofChartLayer *layer, + double x, + double y) +{ + SysprofColumnLayer *self = (SysprofColumnLayer *)layer; + SysprofXYSeries *series; + GListModel *model; + + g_assert (SYSPROF_IS_COLUMN_LAYER (self)); + + if ((series = sysprof_xy_layer_get_series (SYSPROF_XY_LAYER (layer))) && + (model = sysprof_series_get_model (SYSPROF_SERIES (series)))) + { + guint position = sysprof_column_layer_get_index_at_coord (self, x, y, NULL); + + if (position != GTK_INVALID_LIST_POSITION) + return g_list_model_get_item (model, position); + } + + return NULL; +} + +static void +sysprof_column_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofColumnLayer *self = SYSPROF_COLUMN_LAYER (object); + + switch (prop_id) + { + case PROP_COLOR: + g_value_set_boxed (value, &self->color); + break; + + case PROP_HOVER_COLOR: + g_value_set_boxed (value, &self->hover_color); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_column_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofColumnLayer *self = SYSPROF_COLUMN_LAYER (object); + + switch (prop_id) + { + case PROP_COLOR: + sysprof_column_layer_set_color (self, g_value_get_boxed (value)); + break; + + case PROP_HOVER_COLOR: + sysprof_column_layer_set_hover_color (self, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_column_layer_class_init (SysprofColumnLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + SysprofChartLayerClass *chart_layer_class = SYSPROF_CHART_LAYER_CLASS (klass); + + object_class->get_property = sysprof_column_layer_get_property; + object_class->set_property = sysprof_column_layer_set_property; + + widget_class->snapshot = sysprof_column_layer_snapshot; + + chart_layer_class->lookup_item = sysprof_column_layer_lookup_item; + chart_layer_class->snapshot_motion = sysprof_column_layer_snapshot_motion; + + properties[PROP_COLOR] = + g_param_spec_boxed ("color", NULL, NULL, + GDK_TYPE_RGBA, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_HOVER_COLOR] = + g_param_spec_boxed ("hover-color", NULL, NULL, + GDK_TYPE_RGBA, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_column_layer_init (SysprofColumnLayer *self) +{ + self->color = (GdkRGBA) {0,0,0,1}; + self->hover_color = (GdkRGBA) {1,0,0,1}; +} + +SysprofChartLayer * +sysprof_column_layer_new (void) +{ + return g_object_new (SYSPROF_TYPE_COLUMN_LAYER, NULL); +} + +const GdkRGBA * +sysprof_column_layer_get_color (SysprofColumnLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_COLUMN_LAYER (self), NULL); + + return &self->color; +} + +void +sysprof_column_layer_set_color (SysprofColumnLayer *self, + const GdkRGBA *color) +{ + static const GdkRGBA black = {0,0,0,1}; + + g_return_if_fail (SYSPROF_IS_COLUMN_LAYER (self)); + + if (color == NULL) + color = &black; + + if (!gdk_rgba_equal (&self->color, color)) + { + self->color = *color; + self->color_set = color != &black; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLOR]); + + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +const GdkRGBA * +sysprof_column_layer_get_hover_color (SysprofColumnLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_COLUMN_LAYER (self), NULL); + + return &self->hover_color; +} + +void +sysprof_column_layer_set_hover_color (SysprofColumnLayer *self, + const GdkRGBA *hover_color) +{ + static const GdkRGBA red = {1,0,0,1}; + + g_return_if_fail (SYSPROF_IS_COLUMN_LAYER (self)); + + if (hover_color == NULL) + hover_color = &red; + + if (!gdk_rgba_equal (&self->hover_color, hover_color)) + { + self->hover_color = *hover_color; + self->hover_color_set = hover_color != &red; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HOVER_COLOR]); + + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} diff --git a/src/sysprof/sysprof-column-layer.h b/src/sysprof/sysprof-column-layer.h new file mode 100644 index 00000000..6b0dc486 --- /dev/null +++ b/src/sysprof/sysprof-column-layer.h @@ -0,0 +1,46 @@ +/* sysprof-column-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-axis.h" +#include "sysprof-xy-layer.h" +#include "sysprof-xy-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_COLUMN_LAYER (sysprof_column_layer_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofColumnLayer, sysprof_column_layer, SYSPROF, COLUMN_LAYER, SysprofXYLayer) + +SysprofChartLayer *sysprof_column_layer_new (void); +const GdkRGBA *sysprof_column_layer_get_color (SysprofColumnLayer *self); +void sysprof_column_layer_set_color (SysprofColumnLayer *self, + const GdkRGBA *color); +const GdkRGBA *sysprof_column_layer_get_hover_color (SysprofColumnLayer *self); +void sysprof_column_layer_set_hover_color (SysprofColumnLayer *self, + const GdkRGBA *hover_color); + +G_END_DECLS + diff --git a/src/sysprof/sysprof-counters-section.c b/src/sysprof/sysprof-counters-section.c new file mode 100644 index 00000000..8ab20dd8 --- /dev/null +++ b/src/sysprof/sysprof-counters-section.c @@ -0,0 +1,71 @@ +/* sysprof-counters-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart.h" +#include "sysprof-counters-section.h" +#include "sysprof-line-layer.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" +#include "sysprof-xy-series.h" + +struct _SysprofCountersSection +{ + SysprofSection parent_instance; +}; + +G_DEFINE_FINAL_TYPE (SysprofCountersSection, sysprof_counters_section, SYSPROF_TYPE_SECTION) + +static void +sysprof_counters_section_dispose (GObject *object) +{ + SysprofCountersSection *self = (SysprofCountersSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_COUNTERS_SECTION); + + G_OBJECT_CLASS (sysprof_counters_section_parent_class)->dispose (object); +} + +static void +sysprof_counters_section_class_init (SysprofCountersSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_counters_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-counters-section.ui"); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_MARK); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_COUNTER); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_COUNTER_VALUE); + g_type_ensure (SYSPROF_TYPE_LINE_LAYER); + g_type_ensure (SYSPROF_TYPE_TIME_SERIES); + g_type_ensure (SYSPROF_TYPE_TIME_SPAN_LAYER); + g_type_ensure (SYSPROF_TYPE_XY_SERIES); +} + +static void +sysprof_counters_section_init (SysprofCountersSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/sysprof/sysprof-counters-section.h b/src/sysprof/sysprof-counters-section.h new file mode 100644 index 00000000..544fa8bc --- /dev/null +++ b/src/sysprof/sysprof-counters-section.h @@ -0,0 +1,31 @@ +/* sysprof-counters-section.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-section.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_COUNTERS_SECTION (sysprof_counters_section_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofCountersSection, sysprof_counters_section, SYSPROF, COUNTERS_SECTION, SysprofSection) + +G_END_DECLS diff --git a/src/sysprof/sysprof-counters-section.ui b/src/sysprof/sysprof-counters-section.ui new file mode 100644 index 00000000..63df9816 --- /dev/null +++ b/src/sysprof/sysprof-counters-section.ui @@ -0,0 +1,65 @@ + + + + + + diff --git a/src/sysprof/sysprof-cpu-section-counter.ui b/src/sysprof/sysprof-cpu-section-counter.ui new file mode 100644 index 00000000..99cae904 --- /dev/null +++ b/src/sysprof/sysprof-cpu-section-counter.ui @@ -0,0 +1,50 @@ + + + + diff --git a/src/sysprof/sysprof-cpu-section.c b/src/sysprof/sysprof-cpu-section.c new file mode 100644 index 00000000..5aa85bd8 --- /dev/null +++ b/src/sysprof/sysprof-cpu-section.c @@ -0,0 +1,83 @@ +/* sysprof-cpu-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart.h" +#include "sysprof-cpu-section.h" +#include "sysprof-line-layer.h" +#include "sysprof-time-filter-model.h" +#include "sysprof-time-scrubber.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" +#include "sysprof-value-axis.h" +#include "sysprof-xy-series.h" + +struct _SysprofCpuSection +{ + SysprofSection parent_instance; + + SysprofTimeScrubber *scrubber; + GtkColumnView *column_view; + GtkColumnViewColumn *time_column; +}; + +G_DEFINE_FINAL_TYPE (SysprofCpuSection, sysprof_cpu_section, SYSPROF_TYPE_SECTION) + +static void +sysprof_cpu_section_dispose (GObject *object) +{ + SysprofCpuSection *self = (SysprofCpuSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_CPU_SECTION); + + G_OBJECT_CLASS (sysprof_cpu_section_parent_class)->dispose (object); +} + +static void +sysprof_cpu_section_class_init (SysprofCpuSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_cpu_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-cpu-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofCpuSection, column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofCpuSection, scrubber); + gtk_widget_class_bind_template_child (widget_class, SysprofCpuSection, time_column); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_MARK); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_COUNTER); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_COUNTER_VALUE); + g_type_ensure (SYSPROF_TYPE_LINE_LAYER); + g_type_ensure (SYSPROF_TYPE_TIME_SERIES); + g_type_ensure (SYSPROF_TYPE_TIME_SPAN_LAYER); + g_type_ensure (SYSPROF_TYPE_XY_SERIES); +} + +static void +sysprof_cpu_section_init (SysprofCpuSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_column_view_sort_by_column (self->column_view, self->time_column, GTK_SORT_ASCENDING); +} diff --git a/src/libsysprof-ui/sysprof-tab.h b/src/sysprof/sysprof-cpu-section.h similarity index 73% rename from src/libsysprof-ui/sysprof-tab.h rename to src/sysprof/sysprof-cpu-section.h index 5aff386b..995eb0a0 100644 --- a/src/libsysprof-ui/sysprof-tab.h +++ b/src/sysprof/sysprof-cpu-section.h @@ -1,6 +1,6 @@ -/* sysprof-tab.h +/* sysprof-cpu-section.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,16 +20,12 @@ #pragma once -#include - -#include "sysprof-display.h" +#include "sysprof-section.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_TAB (sysprof_tab_get_type()) +#define SYSPROF_TYPE_CPU_SECTION (sysprof_cpu_section_get_type()) -G_DECLARE_FINAL_TYPE (SysprofTab, sysprof_tab, SYSPROF, TAB, GtkWidget) - -GtkWidget *sysprof_tab_new (SysprofDisplay *display); +G_DECLARE_FINAL_TYPE (SysprofCpuSection, sysprof_cpu_section, SYSPROF, CPU_SECTION, SysprofSection) G_END_DECLS diff --git a/src/sysprof/sysprof-cpu-section.ui b/src/sysprof/sysprof-cpu-section.ui new file mode 100644 index 00000000..e808a66d --- /dev/null +++ b/src/sysprof/sysprof-cpu-section.ui @@ -0,0 +1,697 @@ + + + + + + + diff --git a/src/libsysprof/sysprof-flatpak.h b/src/sysprof/sysprof-css-private.h similarity index 84% rename from src/libsysprof/sysprof-flatpak.h rename to src/sysprof/sysprof-css-private.h index 04cc0043..608ced5b 100644 --- a/src/libsysprof/sysprof-flatpak.h +++ b/src/sysprof/sysprof-css-private.h @@ -1,6 +1,6 @@ -/* sysprof-flatpak.h +/* sysprof-css-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,10 +20,10 @@ #pragma once -#include +#include G_BEGIN_DECLS -gchar **sysprof_flatpak_debug_dirs (void); +void _sysprof_css_init (void); G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-marks-page.h b/src/sysprof/sysprof-css.c similarity index 52% rename from src/libsysprof-ui/sysprof-marks-page.h rename to src/sysprof/sysprof-css.c index 5070b1ae..a83b42de 100644 --- a/src/libsysprof-ui/sysprof-marks-page.h +++ b/src/sysprof/sysprof-css.c @@ -1,6 +1,6 @@ -/* sysprof-marks-page.h +/* sysprof-css.c * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,27 +18,25 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#pragma once +#include "config.h" -#include "sysprof-marks-model.h" -#include "sysprof-page.h" -#include "sysprof-zoom-manager.h" +#include "sysprof-resources.h" -G_BEGIN_DECLS +#include "sysprof-css-private.h" -#define SYSPROF_TYPE_MARKS_PAGE (sysprof_marks_page_get_type()) - -G_DECLARE_DERIVABLE_TYPE (SysprofMarksPage, sysprof_marks_page, SYSPROF, MARKS_PAGE, SysprofPage) - -struct _SysprofMarksPageClass +void +_sysprof_css_init (void) { - SysprofPageClass parent_class; + static GtkCssProvider *css; - /*< private >*/ - gpointer _reserved[16]; -}; + if (css == NULL) + { + g_resources_register (sysprof_get_resource ()); -GtkWidget *sysprof_marks_page_new (SysprofZoomManager *zoom_manager, - SysprofMarksModelKind kind); - -G_END_DECLS + css = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (css, "/org/gnome/sysprof/style.css"); + gtk_style_context_add_provider_for_display (gdk_display_get_default (), + GTK_STYLE_PROVIDER (css), + G_MAXUINT); + } +} diff --git a/src/sysprof/sysprof-duplex-layer.c b/src/sysprof/sysprof-duplex-layer.c new file mode 100644 index 00000000..405fe6cc --- /dev/null +++ b/src/sysprof/sysprof-duplex-layer.c @@ -0,0 +1,211 @@ +/* sysprof-duplex-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-duplex-layer.h" + +struct _SysprofDuplexLayer +{ + SysprofChartLayer parent_instance; + SysprofChartLayer *upper_layer; + SysprofChartLayer *lower_layer; +}; + +enum { + PROP_0, + PROP_UPPER_LAYER, + PROP_LOWER_LAYER, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofDuplexLayer, sysprof_duplex_layer, SYSPROF_TYPE_CHART_LAYER) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_duplex_layer_dispose (GObject *object) +{ + SysprofDuplexLayer *self = (SysprofDuplexLayer *)object; + GtkWidget *child; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&self->upper_layer); + g_clear_object (&self->lower_layer); + + G_OBJECT_CLASS (sysprof_duplex_layer_parent_class)->dispose (object); +} + +static void +sysprof_duplex_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofDuplexLayer *self = SYSPROF_DUPLEX_LAYER (object); + + switch (prop_id) + { + case PROP_UPPER_LAYER: + g_value_set_object (value, sysprof_duplex_layer_get_upper_layer (self)); + break; + + case PROP_LOWER_LAYER: + g_value_set_object (value, sysprof_duplex_layer_get_lower_layer (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_duplex_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofDuplexLayer *self = SYSPROF_DUPLEX_LAYER (object); + + switch (prop_id) + { + case PROP_UPPER_LAYER: + sysprof_duplex_layer_set_upper_layer (self, g_value_get_object (value)); + break; + + case PROP_LOWER_LAYER: + sysprof_duplex_layer_set_lower_layer (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_duplex_layer_class_init (SysprofDuplexLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_duplex_layer_dispose; + object_class->get_property = sysprof_duplex_layer_get_property; + object_class->set_property = sysprof_duplex_layer_set_property; + + properties [PROP_UPPER_LAYER] = + g_param_spec_object ("upper-layer", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_LOWER_LAYER] = + g_param_spec_object ("lower-layer", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); +} + +static void +sysprof_duplex_layer_init (SysprofDuplexLayer *self) +{ + g_object_set (gtk_widget_get_layout_manager (GTK_WIDGET (self)), + "orientation", GTK_ORIENTATION_VERTICAL, + NULL); +} + +/** + * sysprof_duplex_layer_get_upper_layer: + * @self: a #SysprofDuplexLayer + * + * Returns: (transfer none) (nullable): the #SysprofChartLayer or %NULL + */ +SysprofChartLayer * +sysprof_duplex_layer_get_upper_layer (SysprofDuplexLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_DUPLEX_LAYER (self), NULL); + + return self->upper_layer; +} + +void +sysprof_duplex_layer_set_upper_layer (SysprofDuplexLayer *self, + SysprofChartLayer *upper_layer) +{ + g_return_if_fail (SYSPROF_IS_DUPLEX_LAYER (self)); + g_return_if_fail (!upper_layer || SYSPROF_IS_CHART_LAYER (upper_layer)); + + if (upper_layer == self->upper_layer) + return; + + if (self->upper_layer) + { + gtk_widget_unparent (GTK_WIDGET (self->upper_layer)); + g_clear_object (&self->upper_layer); + } + + g_set_object (&self->upper_layer, upper_layer); + + if (upper_layer) + gtk_widget_insert_after (GTK_WIDGET (upper_layer), GTK_WIDGET (self), NULL); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPPER_LAYER]); +} + +/** + * sysprof_duplex_layer_get_lower_layer: + * @self: a #SysprofDuplexLayer + * + * Returns: (transfer none) (nullable): the #SysprofChartLayer or %NULL + */ +SysprofChartLayer * +sysprof_duplex_layer_get_lower_layer (SysprofDuplexLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_DUPLEX_LAYER (self), NULL); + + return self->lower_layer; +} + +void +sysprof_duplex_layer_set_lower_layer (SysprofDuplexLayer *self, + SysprofChartLayer *lower_layer) +{ + g_return_if_fail (SYSPROF_IS_DUPLEX_LAYER (self)); + g_return_if_fail (!lower_layer || SYSPROF_IS_CHART_LAYER (lower_layer)); + + if (lower_layer == self->lower_layer) + return; + + if (self->lower_layer) + { + gtk_widget_unparent (GTK_WIDGET (self->lower_layer)); + g_clear_object (&self->lower_layer); + } + + g_set_object (&self->lower_layer, lower_layer); + + if (lower_layer) + gtk_widget_insert_before (GTK_WIDGET (lower_layer), GTK_WIDGET (self), NULL); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOWER_LAYER]); +} diff --git a/src/sysprof/sysprof-duplex-layer.h b/src/sysprof/sysprof-duplex-layer.h new file mode 100644 index 00000000..58c16e7a --- /dev/null +++ b/src/sysprof/sysprof-duplex-layer.h @@ -0,0 +1,39 @@ +/* sysprof-duplex-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-chart-layer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_DUPLEX_LAYER (sysprof_duplex_layer_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofDuplexLayer, sysprof_duplex_layer, SYSPROF, DUPLEX_LAYER, SysprofChartLayer) + +SysprofDuplexLayer *sysprof_duplex_layer_new (void); +SysprofChartLayer *sysprof_duplex_layer_get_upper_layer (SysprofDuplexLayer *self); +void sysprof_duplex_layer_set_upper_layer (SysprofDuplexLayer *self, + SysprofChartLayer *layer); +SysprofChartLayer *sysprof_duplex_layer_get_lower_layer (SysprofDuplexLayer *self); +void sysprof_duplex_layer_set_lower_layer (SysprofDuplexLayer *self, + SysprofChartLayer *layer); + +G_END_DECLS diff --git a/src/sysprof/sysprof-files-section.c b/src/sysprof/sysprof-files-section.c new file mode 100644 index 00000000..b19c3e6b --- /dev/null +++ b/src/sysprof/sysprof-files-section.c @@ -0,0 +1,146 @@ +/* sysprof-files-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include + +#include "sysprof-files-section.h" +#include "sysprof-time-label.h" + +struct _SysprofFilesSection +{ + SysprofSection parent_instance; + + GtkColumnView *column_view; + GtkColumnViewColumn *path_column; +}; + +G_DEFINE_FINAL_TYPE (SysprofFilesSection, sysprof_files_section, SYSPROF_TYPE_SECTION) + +static void +sysprof_files_section_activate_cb (SysprofFilesSection *self, + guint position, + GtkColumnView *view) +{ + g_autoptr(SysprofDocumentFile) file = NULL; + g_autoptr(GtkTextBuffer) buffer = NULL; + g_autoptr(GBytes) bytes = NULL; + GtkSelectionModel *model; + GtkNative *toplevel; + GtkWidget *toolbar; + GtkWidget *header_bar; + GtkWidget *scroller; + GtkWidget *text_view; + const char *str; + const char *endptr; + GtkWindow *window; + gsize len; + + g_assert (SYSPROF_IS_FILES_SECTION (self)); + g_assert (GTK_IS_COLUMN_VIEW (view)); + + model = gtk_column_view_get_model (view); + file = g_list_model_get_item (G_LIST_MODEL (model), position); + bytes = sysprof_document_file_dup_bytes (file); + str = (const char *)g_bytes_get_data (bytes, &len); + endptr = str; + + if (!g_utf8_validate_len (str, len, &endptr) && endptr == str) + return; + + buffer = gtk_text_buffer_new (NULL); + gtk_text_buffer_set_text (buffer, str, endptr - str); + + toplevel = gtk_widget_get_native (GTK_WIDGET (self)); + + window = g_object_new (ADW_TYPE_WINDOW, + "transient-for", toplevel, + "default-width", 800, + "default-height", 600, + "title", sysprof_document_file_get_path (file), + NULL); + header_bar = g_object_new (ADW_TYPE_HEADER_BAR, NULL); + toolbar = g_object_new (ADW_TYPE_TOOLBAR_VIEW, + "top-bar-style", ADW_TOOLBAR_RAISED, + NULL); + scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW, NULL); + text_view = g_object_new (GTK_TYPE_TEXT_VIEW, + "editable", FALSE, + "left-margin", 6, + "right-margin", 6, + "top-margin", 6, + "bottom-margin", 6, + "monospace", TRUE, + "buffer", buffer, + NULL); + + adw_window_set_content (ADW_WINDOW (window), toolbar); + adw_toolbar_view_add_top_bar (ADW_TOOLBAR_VIEW (toolbar), header_bar); + adw_toolbar_view_set_content (ADW_TOOLBAR_VIEW (toolbar), scroller); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroller), text_view); + + gtk_window_present (window); +} + +static char * +format_size (gpointer unused, + guint64 size) +{ + return g_format_size (size); +} + +static void +sysprof_files_section_dispose (GObject *object) +{ + SysprofFilesSection *self = (SysprofFilesSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_FILES_SECTION); + + G_OBJECT_CLASS (sysprof_files_section_parent_class)->dispose (object); +} + +static void +sysprof_files_section_class_init (SysprofFilesSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_files_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-files-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofFilesSection, column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofFilesSection, path_column); + gtk_widget_class_bind_template_callback (widget_class, sysprof_files_section_activate_cb); + gtk_widget_class_bind_template_callback (widget_class, format_size); + + g_type_ensure (SYSPROF_TYPE_DOCUMENT_LOG); + g_type_ensure (SYSPROF_TYPE_TIME_LABEL); +} + +static void +sysprof_files_section_init (SysprofFilesSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_column_view_sort_by_column (self->column_view, self->path_column, GTK_SORT_ASCENDING); +} diff --git a/src/sysprof/sysprof-files-section.h b/src/sysprof/sysprof-files-section.h new file mode 100644 index 00000000..beab1d5c --- /dev/null +++ b/src/sysprof/sysprof-files-section.h @@ -0,0 +1,31 @@ +/* sysprof-files-section.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-section.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_FILES_SECTION (sysprof_files_section_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofFilesSection, sysprof_files_section, SYSPROF, FILES_SECTION, SysprofSection) + +G_END_DECLS diff --git a/src/sysprof/sysprof-files-section.ui b/src/sysprof/sysprof-files-section.ui new file mode 100644 index 00000000..8af1159d --- /dev/null +++ b/src/sysprof/sysprof-files-section.ui @@ -0,0 +1,148 @@ + + + + diff --git a/src/sysprof/sysprof-frame-utility.c b/src/sysprof/sysprof-frame-utility.c new file mode 100644 index 00000000..c3d3f349 --- /dev/null +++ b/src/sysprof/sysprof-frame-utility.c @@ -0,0 +1,163 @@ +/* sysprof-frame-utility.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-frame-utility.h" +#include "sysprof-session.h" + +struct _SysprofFrameUtility +{ + GtkWidget parent_instance; + SysprofSession *session; + SysprofDocumentFrame *frame; +}; + +enum { + PROP_0, + PROP_SESSION, + PROP_FRAME, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofFrameUtility, sysprof_frame_utility, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS]; + +static char * +format_time_offset (SysprofFrameUtility *self, + gint64 time_offset) +{ + int hours; + int minutes; + int seconds; + double time; + + g_assert (SYSPROF_IS_FRAME_UTILITY (self)); + + time = time_offset / (double)SYSPROF_NSEC_PER_SEC; + + hours = time / (60 * 60); + time -= hours * (60 * 60); + + minutes = time / 60; + time -= minutes * 60; + + seconds = time / SYSPROF_NSEC_PER_SEC; + time -= seconds * SYSPROF_NSEC_PER_SEC; + + return g_strdup_printf ("%02d:%02d:%02d.%04d", hours, minutes, seconds, (int)(time * 10000)); +} + +static void +sysprof_frame_utility_finalize (GObject *object) +{ + SysprofFrameUtility *self = (SysprofFrameUtility *)object; + GtkWidget *child; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_FRAME_UTILITY); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (object)))) + gtk_widget_unparent (child); + + g_clear_object (&self->session); + g_clear_object (&self->frame); + + G_OBJECT_CLASS (sysprof_frame_utility_parent_class)->finalize (object); +} + +static void +sysprof_frame_utility_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofFrameUtility *self = SYSPROF_FRAME_UTILITY (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, self->session); + break; + + case PROP_FRAME: + g_value_set_object (value, self->frame); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_frame_utility_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofFrameUtility *self = SYSPROF_FRAME_UTILITY (object); + + switch (prop_id) + { + case PROP_SESSION: + g_set_object (&self->session, g_value_get_object (value)); + break; + + case PROP_FRAME: + g_set_object (&self->frame, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_frame_utility_class_init (SysprofFrameUtilityClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = sysprof_frame_utility_finalize; + object_class->get_property = sysprof_frame_utility_get_property; + object_class->set_property = sysprof_frame_utility_set_property; + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_FRAME] = + g_param_spec_object ("frame", NULL, NULL, + SYSPROF_TYPE_DOCUMENT_FRAME, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-frame-utility.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_bind_template_callback (widget_class, format_time_offset); +} + +static void +sysprof_frame_utility_init (SysprofFrameUtility *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/sysprof/sysprof-frame-utility.h b/src/sysprof/sysprof-frame-utility.h new file mode 100644 index 00000000..298d325e --- /dev/null +++ b/src/sysprof/sysprof-frame-utility.h @@ -0,0 +1,31 @@ +/* sysprof-frame-utility.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_FRAME_UTILITY (sysprof_frame_utility_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofFrameUtility, sysprof_frame_utility, SYSPROF, FRAME_UTILITY, GtkWidget) + +G_END_DECLS diff --git a/src/sysprof/sysprof-frame-utility.ui b/src/sysprof/sysprof-frame-utility.ui new file mode 100644 index 00000000..05bb9373 --- /dev/null +++ b/src/sysprof/sysprof-frame-utility.ui @@ -0,0 +1,137 @@ + + + + diff --git a/src/sysprof/sysprof-greeter.c b/src/sysprof/sysprof-greeter.c new file mode 100644 index 00000000..064bd7e2 --- /dev/null +++ b/src/sysprof/sysprof-greeter.c @@ -0,0 +1,438 @@ +/* sysprof-greeter.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "sysprof-greeter.h" +#include "sysprof-recording-pad.h" + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofCaptureWriter, sysprof_capture_writer_unref) + +struct _SysprofGreeter +{ + AdwWindow parent_instance; + + GFile *file; + + AdwViewStack *view_stack; + GtkBox *toolbar; + AdwPreferencesPage *record_page; + GtkWidget *open_page; + GtkWidget *recent_page; + GtkSwitch *sample_native_stacks; + GtkSwitch *record_disk_usage; + GtkSwitch *record_network_usage; + GtkSwitch *record_compositor; + GtkSwitch *record_system_logs; + GtkSwitch *bundle_symbols; +}; + +enum { + PROP_0, + PROP_FILE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofGreeter, sysprof_greeter, ADW_TYPE_WINDOW) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_greeter_view_stack_notify_visible_child (SysprofGreeter *self, + GParamSpec *pspec, + AdwViewStack *stack) +{ + g_assert (SYSPROF_IS_GREETER (self)); + g_assert (ADW_IS_VIEW_STACK (stack)); + + gtk_widget_set_visible (GTK_WIDGET (self->toolbar), + GTK_WIDGET (self->record_page) == adw_view_stack_get_visible_child (stack)); +} + +static SysprofProfiler * +sysprof_greeter_create_profiler (SysprofGreeter *self) +{ + g_autoptr(SysprofProfiler) profiler = NULL; + + g_assert (SYSPROF_IS_GREETER (self)); + + profiler = sysprof_profiler_new (); + + if (gtk_switch_get_active (self->sample_native_stacks)) + sysprof_profiler_add_instrument (profiler, sysprof_sampler_new ()); + + if (gtk_switch_get_active (self->record_disk_usage)) + sysprof_profiler_add_instrument (profiler, sysprof_disk_usage_new ()); + + if (gtk_switch_get_active (self->record_network_usage)) + sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); + + if (gtk_switch_get_active (self->record_compositor)) + sysprof_profiler_add_instrument (profiler, + sysprof_proxied_instrument_new (G_BUS_TYPE_SESSION, + "org.gnome.Shell", + "/org/gnome/Sysprof3/Profiler")); + + if (gtk_switch_get_active (self->record_system_logs)) + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); + + if (gtk_switch_get_active (self->bundle_symbols)) + sysprof_profiler_add_instrument (profiler, sysprof_symbols_bundle_new ()); + + return g_steal_pointer (&profiler); +} + +static void +sysprof_greeter_record_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofProfiler *profiler = (SysprofProfiler *)object; + g_autoptr(SysprofRecording) recording = NULL; + g_autoptr(SysprofGreeter) self = user_data; + g_autoptr(GError) error = NULL; + + g_assert (SYSPROF_IS_PROFILER (profiler)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_GREETER (self)); + + if (!(recording = sysprof_profiler_record_finish (profiler, result, &error))) + { + /* TODO: error message */ + } + else + { + GtkWidget *pad = sysprof_recording_pad_new (recording); + + gtk_window_present (GTK_WINDOW (pad)); + } + + gtk_window_destroy (GTK_WINDOW (self)); +} + +static void +sysprof_greeter_record_to_memory_action (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofGreeter *self = (SysprofGreeter *)widget; + g_autoptr(SysprofCaptureWriter) writer = NULL; + g_autoptr(SysprofProfiler) profiler = NULL; + g_autofd int fd = -1; + + g_assert (SYSPROF_IS_GREETER (self)); + + fd = sysprof_memfd_create ("[sysprof-profile]"); + + profiler = sysprof_greeter_create_profiler (self); + writer = sysprof_capture_writer_new_from_fd (g_steal_fd (&fd), 0); + + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + + sysprof_profiler_record_async (profiler, + writer, + NULL, + sysprof_greeter_record_cb, + g_object_ref (self)); +} + +static void +sysprof_greeter_choose_file_for_record_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GtkFileDialog *dialog = (GtkFileDialog *)object; + g_autoptr(SysprofGreeter) self = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(GFile) file = NULL; + + g_assert (GTK_IS_FILE_DIALOG (dialog)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_GREETER (self)); + + if ((file = gtk_file_dialog_save_finish (dialog, result, &error))) + { + if (g_file_is_native (file)) + { + g_autoptr(SysprofCaptureWriter) writer = NULL; + g_autoptr(SysprofProfiler) profiler = NULL; + + profiler = sysprof_greeter_create_profiler (self); + writer = sysprof_capture_writer_new (g_file_peek_path (file), 0); + + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + + sysprof_profiler_record_async (profiler, + writer, + NULL, + sysprof_greeter_record_cb, + g_object_ref (self)); + } + else + { + GtkWidget *message; + + message = adw_message_dialog_new (NULL, + _("Must Capture to Local File"), + _("You must choose a local file to capture using Sysprof")); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (message), "close", _("Close")); + gtk_window_present (GTK_WINDOW (message)); + } + } +} + +static void +sysprof_greeter_record_to_file_action (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofGreeter *self = (SysprofGreeter *)widget; + g_autoptr(GtkFileDialog) dialog = NULL; + g_autoptr(GDateTime) now = NULL; + g_autofree char *now_str = NULL; + g_autofree char *initial_name = NULL; + + g_assert (SYSPROF_IS_GREETER (self)); + + now = g_date_time_new_now_local (); + now_str = g_date_time_format (now, "%Y-%m-%d %H:%M:%S"); + initial_name = g_strdup_printf (_("System Capture from %s.syscap"), now_str); + g_strdelimit (initial_name, G_DIR_SEPARATOR_S, '-'); + + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, _("Record to File")); + gtk_file_dialog_set_accept_label (dialog, _("Record")); + gtk_file_dialog_set_modal (dialog, TRUE); + gtk_file_dialog_set_initial_name (dialog, initial_name); + + gtk_file_dialog_save (dialog, + GTK_WINDOW (self), + NULL, + sysprof_greeter_choose_file_for_record_cb, + g_object_ref (self)); +} + +static void +sysprof_greeter_choose_file_for_open_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GtkFileDialog *dialog = (GtkFileDialog *)object; + g_autoptr(SysprofGreeter) self = user_data; + g_autoptr(GError) error = NULL; + g_autoptr(GFile) file = NULL; + + g_assert (GTK_IS_FILE_DIALOG (dialog)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_GREETER (self)); + + if ((file = gtk_file_dialog_open_finish (dialog, result, &error))) + { + if (g_file_is_native (file)) + { + if (g_set_object (&self->file, file)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILE]); + } + else + { + GtkWidget *message; + + message = adw_message_dialog_new (NULL, + _("Must Capture to Local File"), + _("You must choose a local file to capture using Sysprof")); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (message), "close", _("Close")); + gtk_window_present (GTK_WINDOW (message)); + } + } +} + +static void +sysprof_greeter_select_file_action (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofGreeter *self = (SysprofGreeter *)widget; + g_autoptr(GtkFileDialog) dialog = NULL; + g_autoptr(GtkFileFilter) filter = NULL; + g_autoptr(GListStore) filters = NULL; + + g_assert (SYSPROF_IS_GREETER (self)); + + dialog = gtk_file_dialog_new (); + gtk_file_dialog_set_title (dialog, _("Open Recording")); + gtk_file_dialog_set_accept_label (dialog, _("Open")); + gtk_file_dialog_set_modal (dialog, TRUE); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Sysprof Capture (*.syscap)")); + gtk_file_filter_add_mime_type (filter, "application/x-sysprof-capture"); + gtk_file_filter_add_suffix (filter, "syscap"); + + filters = g_list_store_new (GTK_TYPE_FILE_FILTER); + g_list_store_append (filters, filter); + gtk_file_dialog_set_filters (dialog, G_LIST_MODEL (filters)); + + if (self->file) + gtk_file_dialog_set_initial_file (dialog, self->file); + + gtk_file_dialog_open (dialog, + GTK_WINDOW (self), + NULL, + sysprof_greeter_choose_file_for_open_cb, + g_object_ref (self)); +} + +static char * +get_file_path (gpointer unused, + GFile *file) +{ + if (file) + return g_file_get_path (file); + + return NULL; +} + +static void +sysprof_greeter_dispose (GObject *object) +{ + SysprofGreeter *self = (SysprofGreeter *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_GREETER); + + G_OBJECT_CLASS (sysprof_greeter_parent_class)->dispose (object); +} + +static void +sysprof_greeter_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofGreeter *self = SYSPROF_GREETER (object); + + switch (prop_id) + { + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_greeter_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofGreeter *self = SYSPROF_GREETER (object); + + switch (prop_id) + { + case PROP_FILE: + g_set_object (&self->file, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_greeter_class_init (SysprofGreeterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_greeter_dispose; + object_class->get_property = sysprof_greeter_get_property; + object_class->set_property = sysprof_greeter_set_property; + + properties [PROP_FILE] = + g_param_spec_object ("file", NULL, NULL, + G_TYPE_FILE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-greeter.ui"); + + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, bundle_symbols); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, open_page); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, recent_page); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_compositor); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_disk_usage); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_network_usage); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_page); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_system_logs); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_native_stacks); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, toolbar); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, view_stack); + + gtk_widget_class_bind_template_callback (widget_class, sysprof_greeter_view_stack_notify_visible_child); + gtk_widget_class_bind_template_callback (widget_class, get_file_path); + + gtk_widget_class_install_action (widget_class, "win.record-to-memory", NULL, sysprof_greeter_record_to_memory_action); + gtk_widget_class_install_action (widget_class, "win.record-to-file", NULL, sysprof_greeter_record_to_file_action); + gtk_widget_class_install_action (widget_class, "win.select-file", NULL, sysprof_greeter_select_file_action); +} + +static void +sysprof_greeter_init (SysprofGreeter *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget * +sysprof_greeter_new (void) +{ + return g_object_new (SYSPROF_TYPE_GREETER, NULL); +} + +void +sysprof_greeter_set_page (SysprofGreeter *self, + SysprofGreeterPage page) +{ + g_return_if_fail (SYSPROF_IS_GREETER (self)); + + switch (page) + { + case SYSPROF_GREETER_PAGE_OPEN: + adw_view_stack_set_visible_child (self->view_stack, GTK_WIDGET (self->open_page)); + break; + + case SYSPROF_GREETER_PAGE_RECENT: + adw_view_stack_set_visible_child (self->view_stack, GTK_WIDGET (self->recent_page)); + break; + + default: + case SYSPROF_GREETER_PAGE_RECORD: + adw_view_stack_set_visible_child (self->view_stack, GTK_WIDGET (self->record_page)); + break; + } +} diff --git a/src/libsysprof-ui/sysprof-depth-visualizer.h b/src/sysprof/sysprof-greeter.h similarity index 57% rename from src/libsysprof-ui/sysprof-depth-visualizer.h rename to src/sysprof/sysprof-greeter.h index 425c4d42..2b679ab5 100644 --- a/src/libsysprof-ui/sysprof-depth-visualizer.h +++ b/src/sysprof/sysprof-greeter.h @@ -1,6 +1,6 @@ -/* sysprof-depth-visualizer.h +/* sysprof-greeter.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,21 +20,23 @@ #pragma once -#include "sysprof-visualizer.h" +#include G_BEGIN_DECLS -typedef enum +#define SYSPROF_TYPE_GREETER (sysprof_greeter_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofGreeter, sysprof_greeter, SYSPROF, GREETER, AdwWindow) + +typedef enum _SysprofGreeterPage { - SYSPROF_DEPTH_VISUALIZER_COMBINED, - SYSPROF_DEPTH_VISUALIZER_KERNEL_ONLY, - SYSPROF_DEPTH_VISUALIZER_USER_ONLY, -} SysprofDepthVisualizerMode; + SYSPROF_GREETER_PAGE_RECORD, + SYSPROF_GREETER_PAGE_RECENT, + SYSPROF_GREETER_PAGE_OPEN, +} SysprofGreeterPage; -#define SYSPROF_TYPE_DEPTH_VISUALIZER (sysprof_depth_visualizer_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofDepthVisualizer, sysprof_depth_visualizer, SYSPROF, DEPTH_VISUALIZER, SysprofVisualizer) - -SysprofVisualizer *sysprof_depth_visualizer_new (SysprofDepthVisualizerMode mode); +GtkWidget *sysprof_greeter_new (void); +void sysprof_greeter_set_page (SysprofGreeter *self, + SysprofGreeterPage page); G_END_DECLS diff --git a/src/sysprof/sysprof-greeter.ui b/src/sysprof/sysprof-greeter.ui new file mode 100644 index 00000000..61d2cd34 --- /dev/null +++ b/src/sysprof/sysprof-greeter.ui @@ -0,0 +1,407 @@ + + + + + + diff --git a/src/sysprof/sysprof-line-layer.c b/src/sysprof/sysprof-line-layer.c new file mode 100644 index 00000000..36121ade --- /dev/null +++ b/src/sysprof/sysprof-line-layer.c @@ -0,0 +1,569 @@ +/* + * sysprof-line-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-chart-layer-private.h" +#include "sysprof-line-layer.h" +#include "sysprof-xy-layer-private.h" + +#define NEAR_DISTANCE 50 + +struct _SysprofLineLayer +{ + SysprofXYLayer parent_instance; + + GdkRGBA color; + + guint antialias : 1; + guint color_set : 1; + guint dashed : 1; + guint fill : 1; + guint spline : 1; +}; + +struct _SysprofLineLayerClass +{ + SysprofXYLayerClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofLineLayer, sysprof_line_layer, SYSPROF_TYPE_XY_LAYER) + +enum { + PROP_0, + PROP_ANTIALIAS, + PROP_COLOR, + PROP_DASHED, + PROP_FILL, + PROP_SPLINE, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +SysprofChartLayer * +sysprof_line_layer_new (void) +{ + return g_object_new (SYSPROF_TYPE_LINE_LAYER, NULL); +} + +static void +sysprof_line_layer_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofLineLayer *self = (SysprofLineLayer *)widget; + const double *x_values; + const double *y_values; + const GdkRGBA *color; + cairo_t *cr; + double first_x; + double first_y; + double last_x; + double last_y; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_LINE_LAYER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + _sysprof_xy_layer_get_xy (SYSPROF_XY_LAYER (self), &x_values, &y_values, &n_values); + + if (width == 0 || height == 0 || n_values == 0 || self->color.alpha == 0) + return; + + if (self->color_set) + color = &self->color; + else + color = _sysprof_chart_layer_get_accent_bg_color (); + + cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0, -1, width, height+2)); + + if (self->antialias) + cairo_set_antialias (cr, CAIRO_ANTIALIAS_GRAY); + else + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + + cairo_set_line_width (cr, .5); + + cairo_set_matrix (cr, &(cairo_matrix_t) {1, 0, 0, -1, 0, height}); + + first_x = last_x = floor (x_values[0] * width); + first_y = last_y = floor (y_values[0] * height) + .5; + + cairo_move_to (cr, first_x, first_y); + + if (self->spline) + { + for (guint i = 1; i < n_values; i++) + { + double x = floor (x_values[i] * width); + double y = floor (y_values[i] * height) + .5; + + /* Skip if we are getting data incorrectly on the X axis. + * It should have been sorted by this point. + */ + if (x < last_x) + continue; + + cairo_curve_to (cr, + last_x + ((x - last_x)/2), + last_y, + last_x + ((x - last_x)/2), + y, + x, + y); + + last_x = x; + last_y = y; + } + } + else + { + for (guint i = 1; i < n_values; i++) + { + double x = floor (x_values[i] * width); + double y = floor (y_values[i] * height) + .5; + + /* Skip if we are getting data incorrectly on the X axis. + * It should have been sorted by this point. + */ + if (x < last_x) + continue; + + cairo_line_to (cr, x, y); + + last_x = x; + last_y = y; + } + } + + if (self->dashed) + cairo_set_dash (cr, (double[]){2}, 1, 0); + gdk_cairo_set_source_rgba (cr, color); + cairo_stroke_preserve (cr); + + if (self->fill) + { + GdkRGBA fill_color = *color; + + fill_color.alpha *= .25; + gdk_cairo_set_source_rgba (cr, &fill_color); + + cairo_line_to (cr, last_x, .5); + cairo_line_to (cr, first_x, .5); + cairo_line_to (cr, first_x, first_y); + cairo_fill (cr); + } + + cairo_destroy (cr); +} + +static void +sysprof_line_layer_snapshot_motion (SysprofChartLayer *layer, + GtkSnapshot *snapshot, + double x, + double y) +{ + SysprofLineLayer *self = (SysprofLineLayer *)layer; + const GdkRGBA *color; + const double *x_values; + const double *y_values; + double best_distance = G_MAXDOUBLE; + guint best_index = GTK_INVALID_LIST_POSITION; + double best_x = 0; + double best_y = 0; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_LINE_LAYER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + width = gtk_widget_get_width (GTK_WIDGET (self)); + height = gtk_widget_get_height (GTK_WIDGET (self)); + + _sysprof_xy_layer_get_xy (SYSPROF_XY_LAYER (self), &x_values, &y_values, &n_values); + + if (width == 0 || height == 0 || n_values == 0) + return; + + if (self->color_set) + color = &self->color; + else + color = _sysprof_chart_layer_get_accent_bg_color (); + + if (color->alpha == .0) + return; + + for (guint i = 0; i < n_values; i++) + { + double x2 = floor (x_values[i] * width); + double y2 = height - floor (y_values[i] * height); + double distance; + + if (x2 + NEAR_DISTANCE < x) + continue; + + if (x2 > x + NEAR_DISTANCE) + break; + + distance = sqrt (powf (x2 - x, 2) + powf (y2 - y, 2)); + + if (distance < best_distance) + { + best_distance = distance; + best_index = i; + best_x = x2; + best_y = y2; + } + } + + if (best_index != GTK_INVALID_LIST_POSITION) + { + GdkRGBA fill_color; + const int size = 6; + const int half_size = size / 2; + graphene_rect_t area; + cairo_t *cr; + + area = GRAPHENE_RECT_INIT (best_x - half_size, best_y - half_size, size, size); + cr = gtk_snapshot_append_cairo (snapshot, &area); + + cairo_rectangle (cr, area.origin.x, area.origin.y, area.size.width, area.size.height); + + fill_color = *color; + fill_color.alpha *= .75; + gdk_cairo_set_source_rgba (cr, &fill_color); + cairo_fill_preserve (cr); + + gdk_cairo_set_source_rgba (cr, color); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); + + cairo_destroy (cr); + } +} + +static gpointer +sysprof_line_layer_lookup_item (SysprofChartLayer *layer, + double x, + double y) +{ + SysprofLineLayer *self = (SysprofLineLayer *)layer; + SysprofXYSeries *series; + const double *x_values; + const double *y_values; + GListModel *model; + double best_distance = G_MAXDOUBLE; + guint best_index = GTK_INVALID_LIST_POSITION; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_LINE_LAYER (self)); + + width = gtk_widget_get_width (GTK_WIDGET (self)); + height = gtk_widget_get_height (GTK_WIDGET (self)); + + _sysprof_xy_layer_get_xy (SYSPROF_XY_LAYER (self), &x_values, &y_values, &n_values); + + if (width == 0 || height == 0 || n_values == 0) + return NULL; + + series = sysprof_xy_layer_get_series (SYSPROF_XY_LAYER (self)); + model = sysprof_series_get_model (SYSPROF_SERIES (series)); + + for (guint i = 0; i < n_values; i++) + { + double x2 = floor (x_values[i] * width); + double y2 = height - floor (y_values[i] * height); + double distance; + + if (x2 + NEAR_DISTANCE < x) + continue; + + if (x2 > x + NEAR_DISTANCE) + break; + + distance = sqrt (pow (x2 - x, 2) + pow (y2 - y, 2)); + + if (distance < best_distance) + { + best_distance = distance; + best_index = i; + } + } + + if (best_index != GTK_INVALID_LIST_POSITION) + return g_list_model_get_item (model, best_index); + + return NULL; +} + +static void +sysprof_line_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofLineLayer *self = SYSPROF_LINE_LAYER (object); + + switch (prop_id) + { + case PROP_ANTIALIAS: + g_value_set_boolean (value, sysprof_line_layer_get_antialias (self)); + break; + + case PROP_COLOR: + g_value_set_boxed (value, sysprof_line_layer_get_color (self)); + break; + + case PROP_DASHED: + g_value_set_boolean (value, sysprof_line_layer_get_dashed (self)); + break; + + case PROP_FILL: + g_value_set_boolean (value, sysprof_line_layer_get_fill (self)); + break; + + case PROP_SPLINE: + g_value_set_boolean (value, sysprof_line_layer_get_spline (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_line_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofLineLayer *self = SYSPROF_LINE_LAYER (object); + + switch (prop_id) + { + case PROP_ANTIALIAS: + sysprof_line_layer_set_antialias (self, g_value_get_boolean (value)); + break; + + case PROP_COLOR: + sysprof_line_layer_set_color (self, g_value_get_boxed (value)); + break; + + case PROP_DASHED: + sysprof_line_layer_set_dashed (self, g_value_get_boolean (value)); + break; + + case PROP_FILL: + sysprof_line_layer_set_fill (self, g_value_get_boolean (value)); + break; + + case PROP_SPLINE: + sysprof_line_layer_set_spline (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_line_layer_class_init (SysprofLineLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + SysprofChartLayerClass *chart_layer_class = SYSPROF_CHART_LAYER_CLASS (klass); + + object_class->get_property = sysprof_line_layer_get_property; + object_class->set_property = sysprof_line_layer_set_property; + + widget_class->snapshot = sysprof_line_layer_snapshot; + + chart_layer_class->snapshot_motion = sysprof_line_layer_snapshot_motion; + chart_layer_class->lookup_item = sysprof_line_layer_lookup_item; + + properties [PROP_ANTIALIAS] = + g_param_spec_boolean ("antialias", NULL, NULL, + TRUE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_COLOR] = + g_param_spec_boxed ("color", NULL, NULL, + GDK_TYPE_RGBA, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DASHED] = + g_param_spec_boolean ("dashed", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILL] = + g_param_spec_boolean ("fill", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SPLINE] = + g_param_spec_boolean ("spline", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_line_layer_init (SysprofLineLayer *self) +{ + self->color.alpha = 1; + self->antialias = TRUE; +} + +const GdkRGBA * +sysprof_line_layer_get_color (SysprofLineLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_LINE_LAYER (self), NULL); + + return &self->color; +} + +void +sysprof_line_layer_set_color (SysprofLineLayer *self, + const GdkRGBA *color) +{ + static const GdkRGBA black = {0,0,0,1}; + + g_return_if_fail (SYSPROF_IS_LINE_LAYER (self)); + + if (color == NULL) + color = &black; + + if (!gdk_rgba_equal (&self->color, color)) + { + self->color = *color; + self->color_set = color != &black; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLOR]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +gboolean +sysprof_line_layer_get_dashed (SysprofLineLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_LINE_LAYER (self), FALSE); + + return self->dashed; +} + +void +sysprof_line_layer_set_dashed (SysprofLineLayer *self, + gboolean dashed) +{ + g_return_if_fail (SYSPROF_IS_LINE_LAYER (self)); + + dashed = !!dashed; + + if (dashed != self->dashed) + { + self->dashed = dashed; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DASHED]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +gboolean +sysprof_line_layer_get_fill (SysprofLineLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_LINE_LAYER (self), FALSE); + + return self->fill; +} + +void +sysprof_line_layer_set_fill (SysprofLineLayer *self, + gboolean fill) +{ + g_return_if_fail (SYSPROF_IS_LINE_LAYER (self)); + + fill = !!fill; + + if (fill != self->fill) + { + self->fill = fill; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILL]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +gboolean +sysprof_line_layer_get_spline (SysprofLineLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_LINE_LAYER (self), FALSE); + + return self->spline; +} + +void +sysprof_line_layer_set_spline (SysprofLineLayer *self, + gboolean spline) +{ + g_return_if_fail (SYSPROF_IS_LINE_LAYER (self)); + + spline = !!spline; + + if (spline != self->spline) + { + self->spline = spline; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SPLINE]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +gboolean +sysprof_line_layer_get_antialias (SysprofLineLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_LINE_LAYER (self), FALSE); + + return self->antialias; +} + +void +sysprof_line_layer_set_antialias (SysprofLineLayer *self, + gboolean antialias) +{ + g_return_if_fail (SYSPROF_IS_LINE_LAYER (self)); + + antialias = !!antialias; + + if (antialias != self->antialias) + { + self->antialias = antialias; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ANTIALIAS]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} diff --git a/src/sysprof/sysprof-line-layer.h b/src/sysprof/sysprof-line-layer.h new file mode 100644 index 00000000..fe5de457 --- /dev/null +++ b/src/sysprof/sysprof-line-layer.h @@ -0,0 +1,54 @@ +/* + * sysprof-line-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-xy-layer.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_LINE_LAYER (sysprof_line_layer_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofLineLayer, sysprof_line_layer, SYSPROF, LINE_LAYER, SysprofXYLayer) + +SysprofChartLayer *sysprof_line_layer_new (void); +gboolean sysprof_line_layer_get_antialias (SysprofLineLayer *self); +void sysprof_line_layer_set_antialias (SysprofLineLayer *self, + gboolean antialias); +const GdkRGBA *sysprof_line_layer_get_color (SysprofLineLayer *self); +void sysprof_line_layer_set_color (SysprofLineLayer *self, + const GdkRGBA *color); +gboolean sysprof_line_layer_get_dashed (SysprofLineLayer *self); +void sysprof_line_layer_set_dashed (SysprofLineLayer *self, + gboolean dashed); +gboolean sysprof_line_layer_get_fill (SysprofLineLayer *self); +void sysprof_line_layer_set_fill (SysprofLineLayer *self, + gboolean fill); +gboolean sysprof_line_layer_get_flip_y (SysprofLineLayer *self); +void sysprof_line_layer_set_flip_y (SysprofLineLayer *self, + gboolean flip_y); +gboolean sysprof_line_layer_get_spline (SysprofLineLayer *self); +void sysprof_line_layer_set_spline (SysprofLineLayer *self, + gboolean spline); + +G_END_DECLS diff --git a/src/sysprof/sysprof-logs-section.c b/src/sysprof/sysprof-logs-section.c new file mode 100644 index 00000000..595eac95 --- /dev/null +++ b/src/sysprof/sysprof-logs-section.c @@ -0,0 +1,156 @@ +/* sysprof-logs-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-chart.h" +#include "sysprof-document-bitset-index-private.h" +#include "sysprof-frame-utility.h" +#include "sysprof-logs-section.h" +#include "sysprof-time-label.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" + +struct _SysprofLogsSection +{ + SysprofSection parent_instance; + + GtkColumnView *column_view; + GtkColumnViewColumn *time_column; +}; + +G_DEFINE_FINAL_TYPE (SysprofLogsSection, sysprof_logs_section, SYSPROF_TYPE_SECTION) + +static void +sysprof_logs_section_activate_layer_item_cb (SysprofLogsSection *self, + SysprofChartLayer *layer, + SysprofDocumentLog *item, + SysprofChart *chart) +{ + GtkSelectionModel *model; + guint n_items; + + g_assert (SYSPROF_IS_LOGS_SECTION (self)); + g_assert (SYSPROF_IS_CHART_LAYER (layer)); + g_assert (SYSPROF_IS_DOCUMENT_LOG (item)); + g_assert (SYSPROF_IS_CHART (chart)); + + model = gtk_column_view_get_model (self->column_view); + n_items = g_list_model_get_n_items (G_LIST_MODEL (model)); + + for (guint i = 0; i < n_items; i++) + { + g_autoptr(SysprofDocumentLog) log = g_list_model_get_item (G_LIST_MODEL (model), i); + + if (sysprof_document_frame_equal (SYSPROF_DOCUMENT_FRAME (item), SYSPROF_DOCUMENT_FRAME (log))) + { + gtk_single_selection_set_selected (GTK_SINGLE_SELECTION (model), i); + + for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->column_view)); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + if (gtk_widget_activate_action (child, "list.scroll-to-item", "u", i)) + break; + } + + break; + } + } +} + +static char * +format_number (gpointer unused, + guint number) +{ + if (number == 0) + return NULL; + return g_strdup_printf ("%'u", number); +} + +static char * +format_severity (gpointer unused, + GLogLevelFlags severity) +{ + if (severity & G_LOG_LEVEL_CRITICAL) + return g_strdup (_("Critical")); + + if (severity & G_LOG_LEVEL_WARNING) + return g_strdup (_("Warning")); + + if (severity & G_LOG_LEVEL_DEBUG) + return g_strdup (_("Debug")); + + if (severity & G_LOG_LEVEL_MESSAGE) + return g_strdup (_("Message")); + + if (severity & G_LOG_LEVEL_INFO) + return g_strdup (_("Info")); + + if (severity & G_LOG_LEVEL_ERROR) + return g_strdup (_("Error")); + + return g_strdup (""); +} + +static void +sysprof_logs_section_dispose (GObject *object) +{ + SysprofLogsSection *self = (SysprofLogsSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_LOGS_SECTION); + + G_OBJECT_CLASS (sysprof_logs_section_parent_class)->dispose (object); +} + +static void +sysprof_logs_section_class_init (SysprofLogsSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_logs_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-logs-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofLogsSection, column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofLogsSection, time_column); + gtk_widget_class_bind_template_callback (widget_class, format_number); + gtk_widget_class_bind_template_callback (widget_class, format_severity); + gtk_widget_class_bind_template_callback (widget_class, sysprof_logs_section_activate_layer_item_cb); + + g_type_ensure (SYSPROF_TYPE_DOCUMENT_BITSET_INDEX); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_LOG); + g_type_ensure (SYSPROF_TYPE_FRAME_UTILITY); + g_type_ensure (SYSPROF_TYPE_TIME_LABEL); + g_type_ensure (SYSPROF_TYPE_TIME_SERIES); + g_type_ensure (SYSPROF_TYPE_TIME_SPAN_LAYER); +} + +static void +sysprof_logs_section_init (SysprofLogsSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_column_view_sort_by_column (self->column_view, + self->time_column, + GTK_SORT_ASCENDING); +} diff --git a/src/libsysprof-ui/sysprof-cpu-aid.h b/src/sysprof/sysprof-logs-section.h similarity index 72% rename from src/libsysprof-ui/sysprof-cpu-aid.h rename to src/sysprof/sysprof-logs-section.h index 4769885e..32d9a884 100644 --- a/src/libsysprof-ui/sysprof-cpu-aid.h +++ b/src/sysprof/sysprof-logs-section.h @@ -1,6 +1,6 @@ -/* sysprof-cpu-aid.h +/* sysprof-logs-section.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,13 @@ #pragma once -#include "sysprof-aid.h" +#include "sysprof-section.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_CPU_AID (sysprof_cpu_aid_get_type()) +#define SYSPROF_TYPE_LOGS_SECTION (sysprof_logs_section_get_type()) -G_DECLARE_FINAL_TYPE (SysprofCpuAid, sysprof_cpu_aid, SYSPROF, CPU_AID, SysprofAid) - -SysprofAid *sysprof_cpu_aid_new (void); +G_DECLARE_FINAL_TYPE (SysprofLogsSection, sysprof_logs_section, SYSPROF, LOGS_SECTION, SysprofSection) G_END_DECLS + diff --git a/src/sysprof/sysprof-logs-section.ui b/src/sysprof/sysprof-logs-section.ui new file mode 100644 index 00000000..aa44b627 --- /dev/null +++ b/src/sysprof/sysprof-logs-section.ui @@ -0,0 +1,326 @@ + + + + diff --git a/src/sysprof/sysprof-mark-chart-item-private.h b/src/sysprof/sysprof-mark-chart-item-private.h new file mode 100644 index 00000000..4800e900 --- /dev/null +++ b/src/sysprof/sysprof-mark-chart-item-private.h @@ -0,0 +1,40 @@ +/* sysprof-mark-chart-item-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-session.h" +#include "sysprof-time-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARK_CHART_ITEM (sysprof_mark_chart_item_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarkChartItem, sysprof_mark_chart_item, SYSPROF, MARK_CHART_ITEM, GObject) + +SysprofMarkChartItem *sysprof_mark_chart_item_new (SysprofSession *session, + SysprofMarkCatalog *catalog); +SysprofMarkCatalog *sysprof_mark_chart_item_get_catalog (SysprofMarkChartItem *self); +SysprofSession *sysprof_mark_chart_item_get_session (SysprofMarkChartItem *self); +SysprofTimeSeries *sysprof_mark_chart_item_get_series (SysprofMarkChartItem *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-mark-chart-item.c b/src/sysprof/sysprof-mark-chart-item.c new file mode 100644 index 00000000..a3dd0894 --- /dev/null +++ b/src/sysprof/sysprof-mark-chart-item.c @@ -0,0 +1,198 @@ +/* sysprof-mark-chart-item.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-mark-chart-item-private.h" +#include "sysprof-time-filter-model.h" + +struct _SysprofMarkChartItem +{ + GObject parent_instance; + SysprofSession *session; + SysprofMarkCatalog *catalog; + SysprofTimeFilterModel *filtered; + SysprofSeries *series; +}; + +enum { + PROP_0, + PROP_SESSION, + PROP_CATALOG, + PROP_SERIES, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofMarkChartItem, sysprof_mark_chart_item, G_TYPE_OBJECT) + +static GParamSpec *properties[N_PROPS]; + +static void +sysprof_mark_chart_item_constructed (GObject *object) +{ + SysprofMarkChartItem *self = (SysprofMarkChartItem *)object; + + G_OBJECT_CLASS (sysprof_mark_chart_item_parent_class)->constructed (object); + + if (self->catalog == NULL || self->session == NULL) + g_return_if_reached (); + + sysprof_time_filter_model_set_model (self->filtered, G_LIST_MODEL (self->catalog)); + + g_object_bind_property (self->session, "selected-time", + self->filtered, "time-span", + G_BINDING_SYNC_CREATE); +} + +static void +sysprof_mark_chart_item_dispose (GObject *object) +{ + SysprofMarkChartItem *self = (SysprofMarkChartItem *)object; + + g_clear_object (&self->session); + g_clear_object (&self->catalog); + g_clear_object (&self->filtered); + g_clear_object (&self->series); + + G_OBJECT_CLASS (sysprof_mark_chart_item_parent_class)->dispose (object); +} + +static void +sysprof_mark_chart_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkChartItem *self = SYSPROF_MARK_CHART_ITEM (object); + + switch (prop_id) + { + case PROP_CATALOG: + g_value_set_object (value, sysprof_mark_chart_item_get_catalog (self)); + break; + + case PROP_SESSION: + g_value_set_object (value, sysprof_mark_chart_item_get_session (self)); + break; + + case PROP_SERIES: + g_value_set_object (value, sysprof_mark_chart_item_get_series (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_chart_item_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofMarkChartItem *self = SYSPROF_MARK_CHART_ITEM (object); + + switch (prop_id) + { + case PROP_CATALOG: + self->catalog = g_value_dup_object (value); + break; + + case PROP_SESSION: + self->session = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_chart_item_class_init (SysprofMarkChartItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = sysprof_mark_chart_item_constructed; + object_class->dispose = sysprof_mark_chart_item_dispose; + object_class->get_property = sysprof_mark_chart_item_get_property; + object_class->set_property = sysprof_mark_chart_item_set_property; + + properties[PROP_CATALOG] = + g_param_spec_object ("catalog", NULL, NULL, + SYSPROF_TYPE_MARK_CATALOG, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_SERIES] = + g_param_spec_object ("series", NULL, NULL, + SYSPROF_TYPE_TIME_SERIES, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_mark_chart_item_init (SysprofMarkChartItem *self) +{ + self->filtered = sysprof_time_filter_model_new (NULL, NULL); + + self->series = sysprof_time_series_new (NULL, + g_object_ref (G_LIST_MODEL (self->filtered)), + gtk_property_expression_new (SYSPROF_TYPE_DOCUMENT_MARK, NULL, "time"), + gtk_property_expression_new (SYSPROF_TYPE_DOCUMENT_MARK, NULL, "end-time"), + gtk_property_expression_new (SYSPROF_TYPE_DOCUMENT_MARK, NULL, "message")); +} + +SysprofMarkChartItem * +sysprof_mark_chart_item_new (SysprofSession *session, + SysprofMarkCatalog *catalog) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (session), NULL); + g_return_val_if_fail (SYSPROF_IS_MARK_CATALOG (catalog), NULL); + + return g_object_new (SYSPROF_TYPE_MARK_CHART_ITEM, + "session", session, + "catalog", catalog, + NULL); +} + +SysprofMarkCatalog * +sysprof_mark_chart_item_get_catalog (SysprofMarkChartItem *self) +{ + return self->catalog; +} + +SysprofSession * +sysprof_mark_chart_item_get_session (SysprofMarkChartItem *self) +{ + return self->session; +} + +SysprofTimeSeries * +sysprof_mark_chart_item_get_series (SysprofMarkChartItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_CHART_ITEM (self), NULL); + + return SYSPROF_TIME_SERIES (self->series); +} diff --git a/src/sysprof/sysprof-mark-chart-row-private.h b/src/sysprof/sysprof-mark-chart-row-private.h new file mode 100644 index 00000000..ebfb76fc --- /dev/null +++ b/src/sysprof/sysprof-mark-chart-row-private.h @@ -0,0 +1,37 @@ +/* sysprof-mark-chart-row-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-mark-chart-item-private.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARK_CHART_ROW (sysprof_mark_chart_row_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarkChartRow, sysprof_mark_chart_row, SYSPROF, MARK_CHART_ROW, GtkWidget) + +SysprofMarkChartItem *sysprof_mark_chart_row_get_item (SysprofMarkChartRow *self); +void sysprof_mark_chart_row_set_item (SysprofMarkChartRow *self, + SysprofMarkChartItem *item); + +G_END_DECLS diff --git a/src/sysprof/sysprof-mark-chart-row.c b/src/sysprof/sysprof-mark-chart-row.c new file mode 100644 index 00000000..5d41ad81 --- /dev/null +++ b/src/sysprof/sysprof-mark-chart-row.c @@ -0,0 +1,188 @@ +/* sysprof-mark-chart-row.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-mark-chart-row-private.h" +#include "sysprof-chart.h" +#include "sysprof-time-series-item.h" +#include "sysprof-time-span-layer.h" + +struct _SysprofMarkChartRow +{ + GtkWidget parent_instance; + + SysprofMarkChartItem *item; + + SysprofChart *chart; + SysprofTimeSpanLayer *layer; +}; + +enum { + PROP_0, + PROP_ITEM, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofMarkChartRow, sysprof_mark_chart_row, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static gboolean +sysprof_mark_chart_row_activate_layer_item_cb (SysprofMarkChartRow *self, + SysprofChartLayer *layer, + SysprofDocumentMark *mark, + SysprofChart *chart) +{ + SysprofSession *session; + SysprofTimeSpan time_span; + + g_assert (SYSPROF_IS_MARK_CHART_ROW (self)); + g_assert (SYSPROF_IS_CHART_LAYER (layer)); + g_assert (SYSPROF_IS_DOCUMENT_MARK (mark)); + g_assert (SYSPROF_IS_CHART (chart)); + + if (self->item == NULL || + !(session = sysprof_mark_chart_item_get_session (self->item))) + return FALSE; + + time_span.begin_nsec = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (mark)); + time_span.end_nsec = sysprof_document_mark_get_end_time (mark); + + if (sysprof_time_span_duration (time_span) > 0) + { + sysprof_session_select_time (session, &time_span); + sysprof_session_zoom_to_selection (session); + + return TRUE; + } + + return FALSE; +} + +static void +sysprof_mark_chart_row_dispose (GObject *object) +{ + SysprofMarkChartRow *self = (SysprofMarkChartRow *)object; + GtkWidget *child; + + self->chart = NULL; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&self->item); + + G_OBJECT_CLASS (sysprof_mark_chart_row_parent_class)->dispose (object); +} + +static void +sysprof_mark_chart_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkChartRow *self = SYSPROF_MARK_CHART_ROW (object); + + switch (prop_id) + { + case PROP_ITEM: + g_value_set_object (value, sysprof_mark_chart_row_get_item (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_chart_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofMarkChartRow *self = SYSPROF_MARK_CHART_ROW (object); + + switch (prop_id) + { + case PROP_ITEM: + sysprof_mark_chart_row_set_item (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_chart_row_class_init (SysprofMarkChartRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_mark_chart_row_dispose; + object_class->get_property = sysprof_mark_chart_row_get_property; + object_class->set_property = sysprof_mark_chart_row_set_property; + + properties [PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + SYSPROF_TYPE_MARK_CHART_ITEM, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "markchartrow"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-mark-chart-row.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, chart); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkChartRow, layer); + gtk_widget_class_bind_template_callback (widget_class, sysprof_mark_chart_row_activate_layer_item_cb); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_TIME_SERIES_ITEM); + g_type_ensure (SYSPROF_TYPE_TIME_SPAN_LAYER); +} + +static void +sysprof_mark_chart_row_init (SysprofMarkChartRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +SysprofMarkChartItem * +sysprof_mark_chart_row_get_item (SysprofMarkChartRow *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_CHART_ROW (self), NULL); + + return self->item; +} + +void +sysprof_mark_chart_row_set_item (SysprofMarkChartRow *self, + SysprofMarkChartItem *item) +{ + g_return_if_fail (SYSPROF_IS_MARK_CHART_ROW (self)); + g_return_if_fail (!item || SYSPROF_IS_MARK_CHART_ITEM (item)); + + if (g_set_object (&self->item, item)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); +} diff --git a/src/sysprof/sysprof-mark-chart-row.ui b/src/sysprof/sysprof-mark-chart-row.ui new file mode 100644 index 00000000..f11bd532 --- /dev/null +++ b/src/sysprof/sysprof-mark-chart-row.ui @@ -0,0 +1,28 @@ + + + + diff --git a/src/sysprof/sysprof-mark-chart.c b/src/sysprof/sysprof-mark-chart.c new file mode 100644 index 00000000..e83afabe --- /dev/null +++ b/src/sysprof/sysprof-mark-chart.c @@ -0,0 +1,221 @@ +/* sysprof-mark-chart.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-css-private.h" +#include "sysprof-mark-chart.h" +#include "sysprof-mark-chart-item-private.h" +#include "sysprof-mark-chart-row-private.h" + +#include "sysprof-resources.h" + +struct _SysprofMarkChart +{ + GtkWidget parent_instance; + + SysprofSession *session; + + GtkWidget *box; + GtkListView *list_view; +}; + +enum { + PROP_0, + PROP_SESSION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofMarkChart, sysprof_mark_chart, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static gpointer +map_func (gpointer item, + gpointer user_data) +{ + gpointer ret = sysprof_mark_chart_item_new (SYSPROF_SESSION (user_data), SYSPROF_MARK_CATALOG (item)); + g_object_unref (item); + return ret; +} + +static void +sysprof_mark_chart_disconnect (SysprofMarkChart *self) +{ + g_assert (SYSPROF_IS_MARK_CHART (self)); + g_assert (SYSPROF_IS_SESSION (self->session)); + + gtk_list_view_set_model (self->list_view, NULL); +} + +static void +sysprof_mark_chart_connect (SysprofMarkChart *self) +{ + g_autoptr(GtkNoSelection) no = NULL; + GtkFlattenListModel *flatten; + SysprofDocument *document; + GtkMapListModel *map; + + g_assert (SYSPROF_IS_MARK_CHART (self)); + g_assert (SYSPROF_IS_SESSION (self->session)); + + document = sysprof_session_get_document (self->session); + flatten = gtk_flatten_list_model_new (sysprof_document_catalog_marks (document)); + map = gtk_map_list_model_new (G_LIST_MODEL (flatten), + map_func, + g_object_ref (self->session), + g_object_unref); + no = gtk_no_selection_new (G_LIST_MODEL (map)); + + gtk_list_view_set_model (self->list_view, GTK_SELECTION_MODEL (no)); +} + +static void +sysprof_mark_chart_dispose (GObject *object) +{ + SysprofMarkChart *self = (SysprofMarkChart *)object; + + if (self->session) + { + sysprof_mark_chart_disconnect (self); + g_clear_object (&self->session); + } + + g_clear_pointer (&self->box, gtk_widget_unparent); + + G_OBJECT_CLASS (sysprof_mark_chart_parent_class)->dispose (object); +} + +static void +sysprof_mark_chart_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkChart *self = SYSPROF_MARK_CHART (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_mark_chart_get_session (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_chart_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofMarkChart *self = SYSPROF_MARK_CHART (object); + + switch (prop_id) + { + case PROP_SESSION: + sysprof_mark_chart_set_session (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_chart_class_init (SysprofMarkChartClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_mark_chart_dispose; + object_class->get_property = sysprof_mark_chart_get_property; + object_class->set_property = sysprof_mark_chart_set_property; + + properties [PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-mark-chart.ui"); + gtk_widget_class_set_css_name (widget_class, "markchart"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkChart, box); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkChart, list_view); + + g_resources_register (sysprof_get_resource ()); + + g_type_ensure (SYSPROF_TYPE_MARK_CHART_ITEM); + g_type_ensure (SYSPROF_TYPE_MARK_CHART_ROW); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_MARK); +} + +static void +sysprof_mark_chart_init (SysprofMarkChart *self) +{ + _sysprof_css_init (); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget * +sysprof_mark_chart_new (void) +{ + return g_object_new (SYSPROF_TYPE_MARK_CHART, NULL); +} + +/** + * sysprof_mark_chart_get_session: + * @self: a #SysprofMarkChart + * + * Returns: (transfer none) (nullable): a #SysprofSession or %NULL + */ +SysprofSession * +sysprof_mark_chart_get_session (SysprofMarkChart *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_CHART (self), NULL); + + return self->session; +} + +void +sysprof_mark_chart_set_session (SysprofMarkChart *self, + SysprofSession *session) +{ + g_return_if_fail (SYSPROF_IS_MARK_CHART (self)); + g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); + + if (self->session == session) + return; + + if (self->session) + sysprof_mark_chart_disconnect (self); + + g_set_object (&self->session, session); + + if (session) + sysprof_mark_chart_connect (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); +} diff --git a/src/sysprof/sysprof-mark-chart.h b/src/sysprof/sysprof-mark-chart.h new file mode 100644 index 00000000..4f978934 --- /dev/null +++ b/src/sysprof/sysprof-mark-chart.h @@ -0,0 +1,38 @@ +/* sysprof-mark-chart.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARK_CHART (sysprof_mark_chart_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarkChart, sysprof_mark_chart, SYSPROF, MARK_CHART, GtkWidget) + +GtkWidget *sysprof_mark_chart_new (void); +SysprofSession *sysprof_mark_chart_get_session (SysprofMarkChart *self); +void sysprof_mark_chart_set_session (SysprofMarkChart *self, + SysprofSession *session); + +G_END_DECLS diff --git a/src/sysprof/sysprof-mark-chart.ui b/src/sysprof/sysprof-mark-chart.ui new file mode 100644 index 00000000..02e3c200 --- /dev/null +++ b/src/sysprof/sysprof-mark-chart.ui @@ -0,0 +1,88 @@ + + + + diff --git a/src/sysprof/sysprof-mark-table.c b/src/sysprof/sysprof-mark-table.c new file mode 100644 index 00000000..50523b7e --- /dev/null +++ b/src/sysprof/sysprof-mark-table.c @@ -0,0 +1,200 @@ +/* sysprof-mark-table.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-css-private.h" +#include "sysprof-time-filter-model.h" +#include "sysprof-mark-table.h" +#include "sysprof-time-label.h" + +#include "sysprof-resources.h" + +struct _SysprofMarkTable +{ + GtkWidget parent_instance; + + SysprofSession *session; + + GtkWidget *box; + GtkColumnView *column_view; + GtkColumnViewColumn *start_column; +}; + +enum { + PROP_0, + PROP_SESSION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofMarkTable, sysprof_mark_table, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_mark_table_activate_cb (SysprofMarkTable *self, + guint position, + GtkColumnView *column_view) +{ + g_autoptr(SysprofDocumentMark) mark = NULL; + SysprofTimeSpan time_span; + GListModel *model; + + g_assert (SYSPROF_IS_MARK_TABLE (self)); + g_assert (GTK_IS_COLUMN_VIEW (column_view)); + + if (self->session == NULL) + return; + + model = G_LIST_MODEL (gtk_column_view_get_model (column_view)); + mark = g_list_model_get_item (model, position); + + time_span.begin_nsec = sysprof_document_frame_get_time (SYSPROF_DOCUMENT_FRAME (mark)); + time_span.end_nsec = time_span.begin_nsec + sysprof_document_mark_get_duration (mark); + + if (time_span.end_nsec != time_span.begin_nsec) + sysprof_session_select_time (self->session, &time_span); +} + +static void +sysprof_mark_table_dispose (GObject *object) +{ + SysprofMarkTable *self = (SysprofMarkTable *)object; + + if (self->session) + g_clear_object (&self->session); + + g_clear_pointer (&self->box, gtk_widget_unparent); + + G_OBJECT_CLASS (sysprof_mark_table_parent_class)->dispose (object); +} + +static void +sysprof_mark_table_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofMarkTable *self = SYSPROF_MARK_TABLE (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_mark_table_get_session (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_table_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofMarkTable *self = SYSPROF_MARK_TABLE (object); + + switch (prop_id) + { + case PROP_SESSION: + sysprof_mark_table_set_session (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_mark_table_class_init (SysprofMarkTableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_mark_table_dispose; + object_class->get_property = sysprof_mark_table_get_property; + object_class->set_property = sysprof_mark_table_set_property; + + properties [PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-mark-table.ui"); + gtk_widget_class_set_css_name (widget_class, "marktable"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkTable, box); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkTable, column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofMarkTable, start_column); + gtk_widget_class_bind_template_callback (widget_class, sysprof_mark_table_activate_cb); + + g_resources_register (sysprof_get_resource ()); + + g_type_ensure (SYSPROF_TYPE_DOCUMENT_MARK); + g_type_ensure (SYSPROF_TYPE_TIME_FILTER_MODEL); + g_type_ensure (SYSPROF_TYPE_TIME_LABEL); +} + +static void +sysprof_mark_table_init (SysprofMarkTable *self) +{ + _sysprof_css_init (); + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_column_view_sort_by_column (self->column_view, + self->start_column, + GTK_SORT_ASCENDING); +} + +GtkWidget * +sysprof_mark_table_new (void) +{ + return g_object_new (SYSPROF_TYPE_MARK_TABLE, NULL); +} + +/** + * sysprof_mark_table_get_session: + * @self: a #SysprofMarkTable + * + * Returns: (transfer none) (nullable): a #SysprofSession or %NULL + */ +SysprofSession * +sysprof_mark_table_get_session (SysprofMarkTable *self) +{ + g_return_val_if_fail (SYSPROF_IS_MARK_TABLE (self), NULL); + + return self->session; +} + +void +sysprof_mark_table_set_session (SysprofMarkTable *self, + SysprofSession *session) +{ + g_return_if_fail (SYSPROF_IS_MARK_TABLE (self)); + g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); + + if (g_set_object (&self->session, session)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); +} diff --git a/src/sysprof/sysprof-mark-table.h b/src/sysprof/sysprof-mark-table.h new file mode 100644 index 00000000..20c1c277 --- /dev/null +++ b/src/sysprof/sysprof-mark-table.h @@ -0,0 +1,40 @@ +/* sysprof-mark-table.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARK_TABLE (sysprof_mark_table_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarkTable, sysprof_mark_table, SYSPROF, MARK_TABLE, GtkWidget) + +GtkWidget *sysprof_mark_table_new (void); +SysprofSession *sysprof_mark_table_get_session (SysprofMarkTable *self); +void sysprof_mark_table_set_session (SysprofMarkTable *self, + SysprofSession *session); + +G_END_DECLS diff --git a/src/sysprof/sysprof-mark-table.ui b/src/sysprof/sysprof-mark-table.ui new file mode 100644 index 00000000..2181a0cf --- /dev/null +++ b/src/sysprof/sysprof-mark-table.ui @@ -0,0 +1,296 @@ + + + + diff --git a/src/sysprof/sysprof-marks-section.c b/src/sysprof/sysprof-marks-section.c new file mode 100644 index 00000000..7394374a --- /dev/null +++ b/src/sysprof/sysprof-marks-section.c @@ -0,0 +1,88 @@ +/* sysprof-marks-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart.h" +#include "sysprof-mark-chart.h" +#include "sysprof-mark-table.h" +#include "sysprof-marks-section.h" +#include "sysprof-session-model.h" +#include "sysprof-session-model-item.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" + +struct _SysprofMarksSection +{ + SysprofSection parent_instance; + + SysprofMarkChart *mark_chart; + SysprofMarkTable *mark_table; +}; + +G_DEFINE_FINAL_TYPE (SysprofMarksSection, sysprof_marks_section, SYSPROF_TYPE_SECTION) + +static char * +format_number (gpointer unused, + guint number) +{ + if (number == 0) + return NULL; + return g_strdup_printf ("%'u", number); +} + +static void +sysprof_marks_section_dispose (GObject *object) +{ + SysprofMarksSection *self = (SysprofMarksSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_MARKS_SECTION); + + G_OBJECT_CLASS (sysprof_marks_section_parent_class)->dispose (object); +} + +static void +sysprof_marks_section_class_init (SysprofMarksSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_marks_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-marks-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofMarksSection, mark_chart); + gtk_widget_class_bind_template_child (widget_class, SysprofMarksSection, mark_table); + gtk_widget_class_bind_template_callback (widget_class, format_number); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_MARK); + g_type_ensure (SYSPROF_TYPE_MARK_CHART); + g_type_ensure (SYSPROF_TYPE_MARK_TABLE); + g_type_ensure (SYSPROF_TYPE_SESSION_MODEL); + g_type_ensure (SYSPROF_TYPE_SESSION_MODEL_ITEM); + g_type_ensure (SYSPROF_TYPE_TIME_SERIES); + g_type_ensure (SYSPROF_TYPE_TIME_SPAN_LAYER); +} + +static void +sysprof_marks_section_init (SysprofMarksSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/sysprof/sysprof-marks-section.h b/src/sysprof/sysprof-marks-section.h new file mode 100644 index 00000000..c69482c6 --- /dev/null +++ b/src/sysprof/sysprof-marks-section.h @@ -0,0 +1,32 @@ +/* sysprof-marks-section.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-section.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MARKS_SECTION (sysprof_marks_section_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMarksSection, sysprof_marks_section, SYSPROF, MARKS_SECTION, SysprofSection) + +G_END_DECLS + diff --git a/src/sysprof/sysprof-marks-section.ui b/src/sysprof/sysprof-marks-section.ui new file mode 100644 index 00000000..399ab511 --- /dev/null +++ b/src/sysprof/sysprof-marks-section.ui @@ -0,0 +1,211 @@ + + + + + diff --git a/src/sysprof/sysprof-memory-callgraph-view.c b/src/sysprof/sysprof-memory-callgraph-view.c new file mode 100644 index 00000000..97ffded8 --- /dev/null +++ b/src/sysprof/sysprof-memory-callgraph-view.c @@ -0,0 +1,363 @@ +/* sysprof-memory-callgraph-view.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-callgraph-view-private.h" +#include "sysprof-memory-callgraph-view.h" +#include "sysprof-progress-cell-private.h" + +struct _SysprofMemoryCallgraphView +{ + SysprofCallgraphView parent_instance; + + GtkColumnViewColumn *callers_self_column; + GtkColumnViewColumn *callers_total_column; + GtkCustomSorter *callers_self_sorter; + GtkCustomSorter *callers_total_sorter; + + GtkColumnViewColumn *descendants_self_column; + GtkColumnViewColumn *descendants_total_column; + GtkCustomSorter *descendants_self_sorter; + GtkCustomSorter *descendants_total_sorter; + + GtkColumnViewColumn *functions_self_column; + GtkColumnViewColumn *functions_total_column; + GtkCustomSorter *functions_self_sorter; + GtkCustomSorter *functions_total_sorter; +}; + +struct _SysprofMemoryCallgraphViewClass +{ + SysprofCallgraphViewClass parent_class; +}; + +typedef struct _AugmentMemory +{ + gsize size; + gsize total; +} AugmentMemory; + +G_DEFINE_FINAL_TYPE (SysprofMemoryCallgraphView, sysprof_memory_callgraph_view, SYSPROF_TYPE_CALLGRAPH_VIEW) + +static void +augment_memory (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gboolean summarize, + gpointer user_data) +{ + AugmentMemory *cur; + AugmentMemory *sum; + gsize size; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + g_assert (node != NULL); + g_assert (SYSPROF_IS_DOCUMENT_ALLOCATION (frame)); + g_assert (user_data == NULL); + + size = sysprof_document_allocation_get_size (SYSPROF_DOCUMENT_ALLOCATION (frame)); + + cur = sysprof_callgraph_get_augment (callgraph, node); + cur->size += size; + cur->total += size; + + if (summarize) + { + sum = sysprof_callgraph_get_summary_augment (callgraph, node); + sum->size += size; + sum->total += size; + } + + for (node = sysprof_callgraph_node_parent (node); + node != NULL; + node = sysprof_callgraph_node_parent (node)) + { + cur = sysprof_callgraph_get_augment (callgraph, node); + cur->total += size; + + if (summarize) + { + sum = sysprof_callgraph_get_summary_augment (callgraph, node); + sum->total += size; + } + } +} + +static double +get_total_fraction (GObject *item) +{ + g_autoptr(GObject) row = NULL; + + g_object_get (item, "item", &row, NULL); + + if (GTK_IS_TREE_LIST_ROW (row)) + { + GtkTreeListRow *tree_row = GTK_TREE_LIST_ROW (row); + SysprofCallgraphFrame *frame = SYSPROF_CALLGRAPH_FRAME (gtk_tree_list_row_get_item (tree_row)); + SysprofCallgraph *callgraph = sysprof_callgraph_frame_get_callgraph (frame); + AugmentMemory *sum = sysprof_callgraph_frame_get_augment (frame); + AugmentMemory *root = sysprof_callgraph_get_augment (callgraph, NULL); + + if (root->total == 0) + return 0; + + return sum->total / (double)root->total; + } + + return 0; +} + +static double +get_self_fraction (GObject *item) +{ + g_autoptr(GObject) row = NULL; + + g_object_get (item, "item", &row, NULL); + + if (GTK_IS_TREE_LIST_ROW (row)) + { + GtkTreeListRow *tree_row = GTK_TREE_LIST_ROW (row); + SysprofCallgraphFrame *frame = SYSPROF_CALLGRAPH_FRAME (gtk_tree_list_row_get_item (tree_row)); + SysprofCallgraph *callgraph = sysprof_callgraph_frame_get_callgraph (frame); + AugmentMemory *sum = sysprof_callgraph_frame_get_augment (frame); + AugmentMemory *root = sysprof_callgraph_get_augment (callgraph, NULL); + + if (root->total == 0) + return 0; + + return sum->size / (double)root->total; + } + + return .0; +} + +static double +functions_get_total_fraction (GObject *item) +{ + g_autoptr(SysprofCallgraphSymbol) sym = NULL; + + g_object_get (item, "item", &sym, NULL); + + if (SYSPROF_IS_CALLGRAPH_SYMBOL (sym)) + { + SysprofCallgraph *callgraph = sysprof_callgraph_symbol_get_callgraph (sym); + AugmentMemory *sum = sysprof_callgraph_symbol_get_summary_augment (sym); + AugmentMemory *root = sysprof_callgraph_get_augment (callgraph, NULL); + + if (root->total == 0) + return 0; + + return sum->total / (double)root->total; + } + + return 0; +} + +static double +functions_get_self_fraction (GObject *item) +{ + g_autoptr(SysprofCallgraphSymbol) sym = NULL; + + g_object_get (item, "item", &sym, NULL); + + if (SYSPROF_IS_CALLGRAPH_SYMBOL (sym)) + { + SysprofCallgraph *callgraph = sysprof_callgraph_symbol_get_callgraph (sym); + AugmentMemory *sum = sysprof_callgraph_symbol_get_summary_augment (sym); + AugmentMemory *root = sysprof_callgraph_get_augment (callgraph, NULL); + + if (root->total == 0) + return 0; + + return sum->size / (double)root->total; + } + + return 0; +} + +static int +descendants_sort_by_self (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; + SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; + AugmentMemory *aug_a = sysprof_callgraph_frame_get_augment (frame_a); + AugmentMemory *aug_b = sysprof_callgraph_frame_get_augment (frame_b); + AugmentMemory *root = user_data; + double self_a = aug_a->size / (double)root->total; + double self_b = aug_b->size / (double)root->total; + + if (self_a < self_b) + return -1; + else if (self_a > self_b) + return 1; + else + return 0; +} + +static int +descendants_sort_by_total (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; + SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; + AugmentMemory *aug_a = sysprof_callgraph_frame_get_augment (frame_a); + AugmentMemory *aug_b = sysprof_callgraph_frame_get_augment (frame_b); + AugmentMemory *root = user_data; + double total_a = aug_a->total / (double)root->total; + double total_b = aug_b->total / (double)root->total; + + if (total_a < total_b) + return -1; + else if (total_a > total_b) + return 1; + else + return 0; +} + +static int +functions_sort_by_self (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphSymbol *sym_a = (SysprofCallgraphSymbol *)a; + SysprofCallgraphSymbol *sym_b = (SysprofCallgraphSymbol *)b; + AugmentMemory *aug_a = sysprof_callgraph_symbol_get_summary_augment (sym_a); + AugmentMemory *aug_b = sysprof_callgraph_symbol_get_summary_augment (sym_b); + AugmentMemory *root = user_data; + double self_a = aug_a->size / (double)root->total; + double self_b = aug_b->size / (double)root->total; + + if (self_a < self_b) + return -1; + else if (self_a > self_b) + return 1; + else + return 0; +} + +static int +functions_sort_by_total (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphSymbol *sym_a = (SysprofCallgraphSymbol *)a; + SysprofCallgraphSymbol *sym_b = (SysprofCallgraphSymbol *)b; + AugmentMemory *aug_a = sysprof_callgraph_symbol_get_summary_augment (sym_a); + AugmentMemory *aug_b = sysprof_callgraph_symbol_get_summary_augment (sym_b); + AugmentMemory *root = user_data; + double total_a = aug_a->total / (double)root->total; + double total_b = aug_b->total / (double)root->total; + + if (total_a < total_b) + return -1; + else if (total_a > total_b) + return 1; + else + return 0; +} + +static void +sysprof_memory_callgraph_view_load (SysprofCallgraphView *view, + SysprofCallgraph *callgraph) +{ + SysprofMemoryCallgraphView *self = (SysprofMemoryCallgraphView *)view; + AugmentMemory *root; + + g_assert (SYSPROF_IS_MEMORY_CALLGRAPH_VIEW (self)); + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + root = sysprof_callgraph_get_augment (callgraph, NULL); + + gtk_custom_sorter_set_sort_func (self->descendants_self_sorter, + descendants_sort_by_self, root, NULL); + gtk_custom_sorter_set_sort_func (self->descendants_total_sorter, + descendants_sort_by_total, root, NULL); + + gtk_custom_sorter_set_sort_func (self->functions_self_sorter, + functions_sort_by_self, root, NULL); + gtk_custom_sorter_set_sort_func (self->functions_total_sorter, + functions_sort_by_total, root, NULL); + + gtk_custom_sorter_set_sort_func (self->callers_self_sorter, + functions_sort_by_self, root, NULL); + gtk_custom_sorter_set_sort_func (self->callers_total_sorter, + functions_sort_by_total, root, NULL); + + gtk_column_view_sort_by_column (SYSPROF_CALLGRAPH_VIEW (self)->callers_column_view, + self->callers_total_column, + GTK_SORT_DESCENDING); + gtk_column_view_sort_by_column (SYSPROF_CALLGRAPH_VIEW (self)->descendants_column_view, + self->descendants_total_column, + GTK_SORT_DESCENDING); + gtk_column_view_sort_by_column (SYSPROF_CALLGRAPH_VIEW (self)->functions_column_view, + self->functions_total_column, + GTK_SORT_DESCENDING); +} + +static void +sysprof_memory_callgraph_view_class_init (SysprofMemoryCallgraphViewClass *klass) +{ + SysprofCallgraphViewClass *callgraph_view_class = SYSPROF_CALLGRAPH_VIEW_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + callgraph_view_class->augment_size = sizeof (AugmentMemory); + callgraph_view_class->augment_func = augment_memory; + callgraph_view_class->load = sysprof_memory_callgraph_view_load; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-memory-callgraph-view.ui"); + + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, callers_self_column); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, callers_total_column); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, callers_self_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, callers_total_sorter); + + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, descendants_self_column); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, descendants_total_column); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, descendants_self_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, descendants_total_sorter); + + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, functions_self_column); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, functions_total_column); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, functions_self_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofMemoryCallgraphView, functions_total_sorter); + + gtk_widget_class_bind_template_callback (widget_class, get_self_fraction); + gtk_widget_class_bind_template_callback (widget_class, get_total_fraction); + gtk_widget_class_bind_template_callback (widget_class, functions_get_self_fraction); + gtk_widget_class_bind_template_callback (widget_class, functions_get_total_fraction); + + g_type_ensure (SYSPROF_TYPE_PROGRESS_CELL); +} + +static void +sysprof_memory_callgraph_view_init (SysprofMemoryCallgraphView *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget * +sysprof_memory_callgraph_view_new (void) +{ + return g_object_new (SYSPROF_TYPE_MEMORY_CALLGRAPH_VIEW, NULL); +} diff --git a/src/sysprof/sysprof-memory-callgraph-view.h b/src/sysprof/sysprof-memory-callgraph-view.h new file mode 100644 index 00000000..db781720 --- /dev/null +++ b/src/sysprof/sysprof-memory-callgraph-view.h @@ -0,0 +1,40 @@ +/* sysprof-memory-callgraph-view.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-callgraph-view.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_MEMORY_CALLGRAPH_VIEW (sysprof_memory_callgraph_view_get_type()) +#define SYSPROF_IS_MEMORY_CALLGRAPH_VIEW(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_MEMORY_CALLGRAPH_VIEW) +#define SYSPROF_MEMORY_CALLGRAPH_VIEW(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_MEMORY_CALLGRAPH_VIEW, SysprofMemoryCallgraphView) +#define SYSPROF_MEMORY_CALLGRAPH_VIEW_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_MEMORY_CALLGRAPH_VIEW, SysprofMemoryCallgraphViewClass) + +typedef struct _SysprofMemoryCallgraphView SysprofMemoryCallgraphView; +typedef struct _SysprofMemoryCallgraphViewClass SysprofMemoryCallgraphViewClass; + +GType sysprof_memory_callgraph_view_get_type (void) G_GNUC_CONST; +GtkWidget *sysprof_memory_callgraph_view_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofMemoryCallgraphView, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-memory-callgraph-view.ui b/src/sysprof/sysprof-memory-callgraph-view.ui new file mode 100644 index 00000000..0efb97ac --- /dev/null +++ b/src/sysprof/sysprof-memory-callgraph-view.ui @@ -0,0 +1,203 @@ + + + + diff --git a/src/sysprof/sysprof-memory-section.c b/src/sysprof/sysprof-memory-section.c new file mode 100644 index 00000000..3391ad76 --- /dev/null +++ b/src/sysprof/sysprof-memory-section.c @@ -0,0 +1,85 @@ +/* sysprof-memory-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart.h" +#include "sysprof-column-layer.h" +#include "sysprof-memory-callgraph-view.h" +#include "sysprof-memory-section.h" +#include "sysprof-session-model-item.h" +#include "sysprof-session-model.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" +#include "sysprof-value-axis.h" +#include "sysprof-xy-series.h" + +struct _SysprofMemorySection +{ + SysprofSection parent_instance; + + SysprofMemoryCallgraphView *callgraph_view; +}; + +G_DEFINE_FINAL_TYPE (SysprofMemorySection, sysprof_memory_section, SYSPROF_TYPE_SECTION) + +static char * +format_number (gpointer unused, + guint number) +{ + if (number == 0) + return NULL; + return g_strdup_printf ("%'u", number); +} + +static void +sysprof_memory_section_dispose (GObject *object) +{ + SysprofMemorySection *self = (SysprofMemorySection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_MEMORY_SECTION); + + G_OBJECT_CLASS (sysprof_memory_section_parent_class)->dispose (object); +} + +static void +sysprof_memory_section_class_init (SysprofMemorySectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_memory_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-memory-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofMemorySection, callgraph_view); + gtk_widget_class_bind_template_callback (widget_class, format_number); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_XY_SERIES); + g_type_ensure (SYSPROF_TYPE_COLUMN_LAYER); + g_type_ensure (SYSPROF_TYPE_VALUE_AXIS); + g_type_ensure (SYSPROF_TYPE_MEMORY_CALLGRAPH_VIEW); +} + +static void +sysprof_memory_section_init (SysprofMemorySection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/libsysprof-ui/sysprof-logs-aid.h b/src/sysprof/sysprof-memory-section.h similarity index 71% rename from src/libsysprof-ui/sysprof-logs-aid.h rename to src/sysprof/sysprof-memory-section.h index 2f2d13ad..a437d2e7 100644 --- a/src/libsysprof-ui/sysprof-logs-aid.h +++ b/src/sysprof/sysprof-memory-section.h @@ -1,6 +1,6 @@ -/* sysprof-logs-aid.h +/* sysprof-memory-section.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,12 @@ #pragma once -#include "sysprof-aid.h" +#include "sysprof-section.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_LOGS_AID (sysprof_logs_aid_get_type()) +#define SYSPROF_TYPE_MEMORY_SECTION (sysprof_memory_section_get_type()) -G_DECLARE_FINAL_TYPE (SysprofLogsAid, sysprof_logs_aid, SYSPROF, LOGS_AID, SysprofAid) - -SysprofAid *sysprof_logs_aid_new (void); +G_DECLARE_FINAL_TYPE (SysprofMemorySection, sysprof_memory_section, SYSPROF, MEMORY_SECTION, SysprofSection) G_END_DECLS diff --git a/src/sysprof/sysprof-memory-section.ui b/src/sysprof/sysprof-memory-section.ui new file mode 100644 index 00000000..92982f42 --- /dev/null +++ b/src/sysprof/sysprof-memory-section.ui @@ -0,0 +1,145 @@ + + + + + diff --git a/src/sysprof/sysprof-metadata-section.c b/src/sysprof/sysprof-metadata-section.c new file mode 100644 index 00000000..dc2bc31e --- /dev/null +++ b/src/sysprof/sysprof-metadata-section.c @@ -0,0 +1,68 @@ +/* sysprof-metadata-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include + +#include "sysprof-metadata-section.h" +#include "sysprof-time-label.h" + +struct _SysprofMetadataSection +{ + SysprofSection parent_instance; + + GtkColumnView *column_view; +}; + +G_DEFINE_FINAL_TYPE (SysprofMetadataSection, sysprof_metadata_section, SYSPROF_TYPE_SECTION) + +static void +sysprof_metadata_section_dispose (GObject *object) +{ + SysprofMetadataSection *self = (SysprofMetadataSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_METADATA_SECTION); + + G_OBJECT_CLASS (sysprof_metadata_section_parent_class)->dispose (object); +} + +static void +sysprof_metadata_section_class_init (SysprofMetadataSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_metadata_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-metadata-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofMetadataSection, column_view); + + g_type_ensure (SYSPROF_TYPE_DOCUMENT_METADATA); + g_type_ensure (SYSPROF_TYPE_TIME_LABEL); +} + +static void +sysprof_metadata_section_init (SysprofMetadataSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/sysprof/sysprof-metadata-section.h b/src/sysprof/sysprof-metadata-section.h new file mode 100644 index 00000000..a17129a8 --- /dev/null +++ b/src/sysprof/sysprof-metadata-section.h @@ -0,0 +1,31 @@ +/* sysprof-metadata-section.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-section.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_METADATA_SECTION (sysprof_metadata_section_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofMetadataSection, sysprof_metadata_section, SYSPROF, METADATA_SECTION, SysprofSection) + +G_END_DECLS diff --git a/src/sysprof/sysprof-metadata-section.ui b/src/sysprof/sysprof-metadata-section.ui new file mode 100644 index 00000000..cff85ec6 --- /dev/null +++ b/src/sysprof/sysprof-metadata-section.ui @@ -0,0 +1,146 @@ + + + + + diff --git a/src/sysprof/sysprof-normalized-series-item.c b/src/sysprof/sysprof-normalized-series-item.c new file mode 100644 index 00000000..7c666acf --- /dev/null +++ b/src/sysprof/sysprof-normalized-series-item.c @@ -0,0 +1,148 @@ +/* sysprof-normalized-series-item.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-normalized-series-item.h" + +struct _SysprofNormalizedSeriesItem +{ + GObject parent_instance; + GObject *item; + double value; +}; + +enum { + PROP_0, + PROP_ITEM, + PROP_VALUE, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofNormalizedSeriesItem, sysprof_normalized_series_item, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_normalized_series_item_finalize (GObject *object) +{ + SysprofNormalizedSeriesItem *self = (SysprofNormalizedSeriesItem *)object; + + g_clear_object (&self->item); + + G_OBJECT_CLASS (sysprof_normalized_series_item_parent_class)->finalize (object); +} + +static void +sysprof_normalized_series_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofNormalizedSeriesItem *self = SYSPROF_NORMALIZED_SERIES_ITEM (object); + + switch (prop_id) + { + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + case PROP_VALUE: + g_value_set_double (value, self->value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_normalized_series_item_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofNormalizedSeriesItem *self = SYSPROF_NORMALIZED_SERIES_ITEM (object); + + switch (prop_id) + { + case PROP_ITEM: + self->item = g_value_dup_object (value); + break; + + case PROP_VALUE: + self->value = g_value_get_double (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_normalized_series_item_class_init (SysprofNormalizedSeriesItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_normalized_series_item_finalize; + object_class->get_property = sysprof_normalized_series_item_get_property; + object_class->set_property = sysprof_normalized_series_item_set_property; + + properties [PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_VALUE] = + g_param_spec_double ("value", NULL, NULL, + 0, 1, 0, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_normalized_series_item_init (SysprofNormalizedSeriesItem *self) +{ +} + +double +sysprof_normalized_series_item_get_value (SysprofNormalizedSeriesItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES_ITEM (self), 0); + + return self->value; +} + +/** + * sysprof_normalized_series_item_get_item: + * @self: a #SysprofNormalizedSeriesItem + * + * Gets the underlying item the normalized value was calculated for. + * + * Returns: (transfer none) (type GObject): a #GObject + */ +gpointer +sysprof_normalized_series_item_get_item (SysprofNormalizedSeriesItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES_ITEM (self), NULL); + + return self->item; +} diff --git a/src/sysprof/sysprof-normalized-series-item.h b/src/sysprof/sysprof-normalized-series-item.h new file mode 100644 index 00000000..6b2d3e9f --- /dev/null +++ b/src/sysprof/sysprof-normalized-series-item.h @@ -0,0 +1,36 @@ +/* sysprof-normalized-series-item.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_NORMALIZED_SERIES_ITEM (sysprof_normalized_series_item_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofNormalizedSeriesItem, sysprof_normalized_series_item, SYSPROF, NORMALIZED_SERIES_ITEM, GObject) + +gpointer sysprof_normalized_series_item_get_item (SysprofNormalizedSeriesItem *self); +double sysprof_normalized_series_item_get_value (SysprofNormalizedSeriesItem *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-normalized-series.c b/src/sysprof/sysprof-normalized-series.c new file mode 100644 index 00000000..cd2e563b --- /dev/null +++ b/src/sysprof/sysprof-normalized-series.c @@ -0,0 +1,566 @@ +/* sysprof-normalized-series.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "eggbitset.h" + +#include "sysprof-axis-private.h" +#include "sysprof-normalized-series.h" +#include "sysprof-normalized-series-item.h" +#include "sysprof-scheduler-private.h" +#include "sysprof-series-private.h" + +#define SYSPROF_NORMALIZED_SERIES_STEP_TIME_USEC (1000) /* 1 msec */ + +struct _SysprofNormalizedSeries +{ + SysprofSeries parent_instance; + + SysprofSeries *series; + SysprofAxis *axis; + GtkExpression *expression; + GArray *values; + EggBitset *missing; + + gulong range_changed_handler; + gsize scheduled_update; + + guint disposed : 1; + guint inverted : 1; +}; + +struct _SysprofNormalizedSeriesClass +{ + SysprofSeriesClass parent_class; +}; + +enum { + PROP_0, + PROP_AXIS, + PROP_EXPRESSION, + PROP_INVERTED, + PROP_SERIES, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofNormalizedSeries, sysprof_normalized_series, SYSPROF_TYPE_SERIES) + +static GParamSpec *properties [N_PROPS]; + +static gboolean +sysprof_normalized_series_update_missing (gint64 deadline, + gpointer user_data) +{ + SysprofNormalizedSeries *self = user_data; + g_autoptr(GtkExpression) expression = NULL; + g_autoptr(GListModel) model = NULL; + g_autoptr(EggBitset) bitset = NULL; + EggBitsetIter iter; + guint position; + + g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); + + if (self->missing == NULL || self->disposed || self->axis == NULL || self->series == NULL || self->expression == NULL) + { + self->scheduled_update = 0; + return G_SOURCE_REMOVE; + } + + bitset = egg_bitset_ref (self->missing); + model = g_object_ref (sysprof_series_get_model (self->series)); + expression = gtk_expression_ref (self->expression); + + if (egg_bitset_iter_init_first (&iter, bitset, &position)) + { + guint count = 0; + guint first = position; + + for (;;) + { + g_autoptr(GObject) item = g_list_model_get_item (model, position); + g_auto(GValue) value = G_VALUE_INIT; + guint next = GTK_INVALID_LIST_POSITION; + double *fval = &g_array_index (self->values, double, position); + gboolean expired; + + gtk_expression_evaluate (expression, item, &value); + + g_assert (self->values->len > position); + + count++; + + if (!self->inverted) + *fval = _sysprof_axis_normalize (self->axis, &value); + else + *fval = 1. - _sysprof_axis_normalize (self->axis, &value); + + egg_bitset_remove (bitset, position); + + if (self->disposed) + break; + + /* Only do expiry check every 10 items */ + expired = count % 10 == 0 && g_get_monotonic_time () >= deadline; + + if (!egg_bitset_iter_init_first (&iter, bitset, &next) || + next != position + 1 || + expired) + { + g_list_model_items_changed (G_LIST_MODEL (self), + first, + position-first+1, + position-first+1); + first = next; + } + + if (expired || next == GTK_INVALID_LIST_POSITION) + break; + + position = next; + } + } + + if (egg_bitset_is_empty (bitset)) + { + self->scheduled_update = 0; + return G_SOURCE_REMOVE; + } + + return G_SOURCE_CONTINUE; +} + +static void +sysprof_normalized_series_maybe_update (SysprofNormalizedSeries *self) +{ + g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); + + if (self->scheduled_update || self->disposed) + return; + + if (!self->missing || egg_bitset_is_empty (self->missing)) + return; + + self->scheduled_update = sysprof_scheduler_add (sysprof_normalized_series_update_missing, self); +} + +static void +sysprof_normalized_series_items_changed (SysprofSeries *series, + GListModel *model, + guint position, + guint removed, + guint added) +{ + SysprofNormalizedSeries *self = (SysprofNormalizedSeries *)series; + + g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); + g_assert (G_IS_LIST_MODEL (model)); + + egg_bitset_splice (self->missing, position, removed, added); + egg_bitset_add_range (self->missing, position, added); + + if (removed > 0) + g_array_remove_range (self->values, position, removed); + + if (added > 0) + { + if (position == self->values->len) + { + g_array_set_size (self->values, self->values->len + added); + } + else + { + static const double empty[32] = {0}; + const double *vals = empty; + double *alloc = NULL; + + if (added > G_N_ELEMENTS (empty)) + vals = alloc = g_new0 (double, added); + + g_array_insert_vals (self->values, position, vals, added); + + g_free (alloc); + } + } + + SYSPROF_SERIES_CLASS (sysprof_normalized_series_parent_class)->items_changed (series, model, position, removed, added); + + sysprof_normalized_series_maybe_update (self); +} + +static void +sysprof_normalized_series_invalidate (SysprofNormalizedSeries *self) +{ + guint n_items; + + g_assert (SYSPROF_IS_NORMALIZED_SERIES (self)); + + n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + + if (n_items > 0) + { + egg_bitset_remove_all (self->missing); + egg_bitset_add_range (self->missing, 0, n_items); + + g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, n_items); + + sysprof_normalized_series_maybe_update (self); + } +} + +static gpointer +sysprof_normalized_series_get_series_item (SysprofSeries *series, + guint position, + gpointer item) +{ + SysprofNormalizedSeries *self = SYSPROF_NORMALIZED_SERIES (series); + SysprofNormalizedSeriesItem *ret; + + ret = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES_ITEM, + "item", item, + "value", g_array_index (self->values, double, position), + NULL); + + g_object_unref (item); + + return ret; +} + +static void +sysprof_normalized_series_dispose (GObject *object) +{ + SysprofNormalizedSeries *self = (SysprofNormalizedSeries *)object; + + self->disposed = TRUE; + + sysprof_scheduler_clear (&self->scheduled_update); + g_clear_signal_handler (&self->range_changed_handler, self->axis); + + g_clear_object (&self->axis); + g_clear_object (&self->series); + + g_clear_pointer (&self->expression, gtk_expression_unref); + + G_OBJECT_CLASS (sysprof_normalized_series_parent_class)->dispose (object); +} + +static void +sysprof_normalized_series_finalize (GObject *object) +{ + SysprofNormalizedSeries *self = (SysprofNormalizedSeries *)object; + + g_clear_pointer (&self->missing, egg_bitset_unref); + g_clear_pointer (&self->values, g_array_unref); + + G_OBJECT_CLASS (sysprof_normalized_series_parent_class)->finalize (object); +} + +static void +sysprof_normalized_series_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofNormalizedSeries *self = SYSPROF_NORMALIZED_SERIES (object); + + switch (prop_id) + { + case PROP_AXIS: + g_value_set_object (value, sysprof_normalized_series_get_axis (self)); + break; + + case PROP_EXPRESSION: + gtk_value_set_expression (value, sysprof_normalized_series_get_expression (self)); + break; + + case PROP_SERIES: + g_value_set_object (value, sysprof_normalized_series_get_series (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_normalized_series_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofNormalizedSeries *self = SYSPROF_NORMALIZED_SERIES (object); + + switch (prop_id) + { + case PROP_AXIS: + sysprof_normalized_series_set_axis (self, g_value_get_object (value)); + break; + + case PROP_EXPRESSION: + sysprof_normalized_series_set_expression (self, gtk_value_get_expression (value)); + break; + + case PROP_SERIES: + sysprof_normalized_series_set_series (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_normalized_series_class_init (SysprofNormalizedSeriesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSeriesClass *series_class = SYSPROF_SERIES_CLASS (klass); + + object_class->dispose = sysprof_normalized_series_dispose; + object_class->finalize = sysprof_normalized_series_finalize; + object_class->get_property = sysprof_normalized_series_get_property; + object_class->set_property = sysprof_normalized_series_set_property; + + series_class->series_item_type = SYSPROF_TYPE_NORMALIZED_SERIES_ITEM; + series_class->get_series_item = sysprof_normalized_series_get_series_item; + series_class->items_changed = sysprof_normalized_series_items_changed; + + properties [PROP_AXIS] = + g_param_spec_object ("axis", NULL, NULL, + SYSPROF_TYPE_AXIS, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_EXPRESSION] = + gtk_param_spec_expression ("expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_INVERTED] = + g_param_spec_boolean ("inverted", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SERIES] = + g_param_spec_object ("series", NULL, NULL, + SYSPROF_TYPE_SERIES, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_normalized_series_init (SysprofNormalizedSeries *self) +{ + self->values = g_array_new (FALSE, TRUE, sizeof (double)); + self->missing = egg_bitset_new_empty (); +} + +/** + * sysprof_normalized_series_new: + * @series: (transfer-full) (nullable): a #SysprofSeries + * @axis: (transfer-full) (nullable): a #SysprofAxis + * @expression: (transfer-full) (nullable): a #GtkExpression + * + * Creates a new series that will normalize values from @series + * by extracting values using @expression and determining where + * they fall between the axis's range. + * + * Returns: (transfer full) (type SysprofNormalizedSeries): a #SysprofNormalizedSeries + */ +SysprofSeries * +sysprof_normalized_series_new (SysprofSeries *series, + SysprofAxis *axis, + GtkExpression *expression) +{ + SysprofNormalizedSeries *normalized; + + g_return_val_if_fail (!series || SYSPROF_IS_SERIES (series), NULL); + g_return_val_if_fail (!axis || SYSPROF_IS_AXIS (axis), NULL); + g_return_val_if_fail (!expression || GTK_IS_EXPRESSION (expression), NULL); + + normalized = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES, + "axis", axis, + "series", series, + NULL); + + g_clear_object (&series); + g_clear_object (&axis); + g_clear_pointer (&expression, gtk_expression_unref); + + return SYSPROF_SERIES (normalized); +} + +double +sysprof_normalized_series_get_value_at (SysprofNormalizedSeries *self, + guint position) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), .0f); + + if (egg_bitset_contains (self->missing, position)) + return .0; + + if (position >= self->values->len) + return .0; + + return g_array_index (self->values, double, position); +} + +/** + * sysprof_normalized_series_get_axis: + * @self: a #SysprofNormalizedSeries + * + * Gets the axis used to normalize values. + * + * Returns: (transfer none) (nullable): a #SysprofAxis or %NULL + */ +SysprofAxis * +sysprof_normalized_series_get_axis (SysprofNormalizedSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); + + return self->axis; +} + +void +sysprof_normalized_series_set_axis (SysprofNormalizedSeries *self, + SysprofAxis *axis) +{ + g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); + g_return_if_fail (!axis || SYSPROF_IS_AXIS (axis)); + + if (self->axis == axis) + return; + + if (self->axis) + { + g_clear_signal_handler (&self->range_changed_handler, self->axis); + g_clear_object (&self->axis); + } + + if (axis) + { + self->axis = g_object_ref (axis); + self->range_changed_handler = + g_signal_connect_object (axis, + "range-changed", + G_CALLBACK (sysprof_normalized_series_invalidate), + self, + G_CONNECT_SWAPPED); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AXIS]); + + sysprof_normalized_series_invalidate (self); +} + +/** + * sysprof_normalized_series_get_expression: + * @self: a #SysprofNormalizedSeries + * + * Gets the expression to used to extract values from items within + * #SysprofNormalizedSeries:series. + * + * Returns: (transfer none) (nullable): a #GtkExpression or %NULL + */ +GtkExpression * +sysprof_normalized_series_get_expression (SysprofNormalizedSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); + + return self->expression; +} + +void +sysprof_normalized_series_set_expression (SysprofNormalizedSeries *self, + GtkExpression *expression) +{ + g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); + g_return_if_fail (!expression || GTK_IS_EXPRESSION (expression)); + + if (expression == self->expression) + return; + + if (expression) + gtk_expression_ref (expression); + + g_clear_pointer (&self->expression, gtk_expression_unref); + + self->expression = expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); +} + +/** + * sysprof_normalized_series_get_series: + * @self: a #SysprofNormalizedSeries + * + * Gets the series containing values to normalize. + * + * Returns: (transfer none) (nullable): a #SysprofSeries or %NULL + */ +SysprofSeries * +sysprof_normalized_series_get_series (SysprofNormalizedSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); + + return self->series; +} + +void +sysprof_normalized_series_set_series (SysprofNormalizedSeries *self, + SysprofSeries *series) +{ + g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); + g_return_if_fail (!series || SYSPROF_IS_SERIES (series)); + + if (g_set_object (&self->series, series)) + { + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SERIES]); + sysprof_series_set_model (SYSPROF_SERIES (self), G_LIST_MODEL (series)); + } +} + +const double * +sysprof_normalized_series_get_values (SysprofNormalizedSeries *self, + guint *n_values) +{ + g_return_val_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self), NULL); + + if (self->values == NULL || self->values->len == 0) + return NULL; + + *n_values = self->values->len; + + return &g_array_index (self->values, double, 0); +} + +void +sysprof_normalized_series_set_inverted (SysprofNormalizedSeries *self, + gboolean inverted) +{ + g_return_if_fail (SYSPROF_IS_NORMALIZED_SERIES (self)); + + inverted = !!inverted; + + if (inverted != self->inverted) + { + self->inverted = inverted; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INVERTED]); + sysprof_normalized_series_invalidate (self); + } +} diff --git a/src/sysprof/sysprof-normalized-series.h b/src/sysprof/sysprof-normalized-series.h new file mode 100644 index 00000000..ff93af89 --- /dev/null +++ b/src/sysprof/sysprof-normalized-series.h @@ -0,0 +1,61 @@ +/* sysprof-normalized-series.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-axis.h" +#include "sysprof-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_NORMALIZED_SERIES (sysprof_normalized_series_get_type()) +#define SYSPROF_IS_NORMALIZED_SERIES(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_NORMALIZED_SERIES)) +#define SYSPROF_NORMALIZED_SERIES(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_NORMALIZED_SERIES, SysprofNormalizedSeries)) +#define SYSPROF_NORMALIZED_SERIES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_NORMALIZED_SERIES, SysprofNormalizedSeriesClass)) + +typedef struct _SysprofNormalizedSeries SysprofNormalizedSeries; +typedef struct _SysprofNormalizedSeriesClass SysprofNormalizedSeriesClass; + +GType sysprof_normalized_series_get_type (void) G_GNUC_CONST; +SysprofSeries *sysprof_normalized_series_new (SysprofSeries *series, + SysprofAxis *axis, + GtkExpression *expression); +gboolean sysprof_normalized_series_get_inverted (SysprofNormalizedSeries *self); +void sysprof_normalized_series_set_inverted (SysprofNormalizedSeries *self, + gboolean inverted); +GtkExpression *sysprof_normalized_series_get_expression (SysprofNormalizedSeries *self); +void sysprof_normalized_series_set_expression (SysprofNormalizedSeries *self, + GtkExpression *expression); +SysprofAxis *sysprof_normalized_series_get_axis (SysprofNormalizedSeries *self); +void sysprof_normalized_series_set_axis (SysprofNormalizedSeries *self, + SysprofAxis *axis); +SysprofSeries *sysprof_normalized_series_get_series (SysprofNormalizedSeries *self); +void sysprof_normalized_series_set_series (SysprofNormalizedSeries *self, + SysprofSeries *series); +double sysprof_normalized_series_value_at (SysprofNormalizedSeries *self, + guint position); +const double *sysprof_normalized_series_get_values (SysprofNormalizedSeries *self, + guint *n_values); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofNormalizedSeries, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-process-dialog.c b/src/sysprof/sysprof-process-dialog.c new file mode 100644 index 00000000..80b624a5 --- /dev/null +++ b/src/sysprof/sysprof-process-dialog.c @@ -0,0 +1,144 @@ +/* sysprof-process-dialog.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-process-dialog.h" + +struct _SysprofProcessDialog +{ + AdwWindow parent_instance; + SysprofDocumentProcess *process; +}; + +enum { + PROP_0, + PROP_PROCESS, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofProcessDialog, sysprof_process_dialog, ADW_TYPE_WINDOW) + +static GParamSpec *properties [N_PROPS]; + +static char * +format_address (gpointer unused, + SysprofAddress addr) +{ + return g_strdup_printf ("0x%"G_GINT64_MODIFIER"x", addr); +} + +static void +sysprof_process_dialog_dispose (GObject *object) +{ + SysprofProcessDialog *self = (SysprofProcessDialog *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_PROCESS_DIALOG); + + g_clear_object (&self->process); + + G_OBJECT_CLASS (sysprof_process_dialog_parent_class)->dispose (object); +} + +static void +sysprof_process_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofProcessDialog *self = SYSPROF_PROCESS_DIALOG (object); + + switch (prop_id) + { + case PROP_PROCESS: + g_value_set_object (value, sysprof_process_dialog_get_process (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_process_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofProcessDialog *self = SYSPROF_PROCESS_DIALOG (object); + + switch (prop_id) + { + case PROP_PROCESS: + sysprof_process_dialog_set_process (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_process_dialog_class_init (SysprofProcessDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_process_dialog_dispose; + object_class->get_property = sysprof_process_dialog_get_property; + object_class->set_property = sysprof_process_dialog_set_property; + + properties [PROP_PROCESS] = + g_param_spec_object ("process", NULL, NULL, + SYSPROF_TYPE_DOCUMENT_PROCESS, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-process-dialog.ui"); + gtk_widget_class_bind_template_callback (widget_class, format_address); + + g_type_ensure (SYSPROF_TYPE_THREAD_INFO); +} + +static void +sysprof_process_dialog_init (SysprofProcessDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +SysprofDocumentProcess * +sysprof_process_dialog_get_process (SysprofProcessDialog *self) +{ + g_return_val_if_fail (SYSPROF_IS_PROCESS_DIALOG (self), NULL); + + return self->process; +} + +void +sysprof_process_dialog_set_process (SysprofProcessDialog *self, + SysprofDocumentProcess *process) +{ + g_return_if_fail (SYSPROF_IS_PROCESS_DIALOG (self)); + g_return_if_fail (!process || SYSPROF_IS_DOCUMENT_PROCESS (process)); + + if (g_set_object (&self->process, process)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PROCESS]); +} diff --git a/src/sysprof/sysprof-process-dialog.h b/src/sysprof/sysprof-process-dialog.h new file mode 100644 index 00000000..c7bedc8c --- /dev/null +++ b/src/sysprof/sysprof-process-dialog.h @@ -0,0 +1,37 @@ +/* sysprof-process-dialog.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_PROCESS_DIALOG (sysprof_process_dialog_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofProcessDialog, sysprof_process_dialog, SYSPROF, PROCESS_DIALOG, AdwWindow) + +SysprofDocumentProcess *sysprof_process_dialog_get_process (SysprofProcessDialog *self); +void sysprof_process_dialog_set_process (SysprofProcessDialog *self, + SysprofDocumentProcess *process); + +G_END_DECLS diff --git a/src/sysprof/sysprof-process-dialog.ui b/src/sysprof/sysprof-process-dialog.ui new file mode 100644 index 00000000..4fd53985 --- /dev/null +++ b/src/sysprof/sysprof-process-dialog.ui @@ -0,0 +1,678 @@ + + + + diff --git a/src/sysprof/sysprof-processes-section.c b/src/sysprof/sysprof-processes-section.c new file mode 100644 index 00000000..fbf1e39d --- /dev/null +++ b/src/sysprof/sysprof-processes-section.c @@ -0,0 +1,165 @@ +/* sysprof-processes-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include + +#include "sysprof-chart-layer.h" +#include "sysprof-chart.h" +#include "sysprof-document-process.h" +#include "sysprof-process-dialog.h" +#include "sysprof-processes-section.h" +#include "sysprof-session-model-item.h" +#include "sysprof-session-model.h" +#include "sysprof-single-model.h" +#include "sysprof-time-label.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" +#include "sysprof-value-axis.h" + +struct _SysprofProcessesSection +{ + SysprofSection parent_instance; + + GtkListView *list_view; + GtkColumnView *column_view; + GtkColumnViewColumn *time_column; +}; + +G_DEFINE_FINAL_TYPE (SysprofProcessesSection, sysprof_processes_section, SYSPROF_TYPE_SECTION) + +static char * +format_number (gpointer unused, + guint number) +{ + if (number == 0) + return NULL; + return g_strdup_printf ("%'u", number); +} + +static void +show_dialog (GtkNative *transient_for, + SysprofDocumentProcess *process) +{ + SysprofProcessDialog *dialog; + g_autofree char *title = NULL; + + g_assert (!transient_for || GTK_IS_NATIVE (transient_for)); + g_assert (SYSPROF_IS_DOCUMENT_PROCESS (process)); + + title = sysprof_document_process_dup_title (process); + + dialog = g_object_new (SYSPROF_TYPE_PROCESS_DIALOG, + "process", process, + "transient-for", transient_for, + "title", title, + NULL); + + gtk_window_present (GTK_WINDOW (dialog)); +} + +static void +activate_layer_item_cb (GtkListItem *list_item, + SysprofChartLayer *layer, + SysprofDocumentProcess *process, + SysprofChart *chart) +{ + + GtkNative *transient_for; + + g_assert (GTK_IS_LIST_ITEM (list_item)); + g_assert (SYSPROF_IS_CHART_LAYER (layer)); + g_assert (SYSPROF_IS_DOCUMENT_PROCESS (process)); + g_assert (SYSPROF_IS_CHART (chart)); + + transient_for = gtk_widget_get_native (GTK_WIDGET (chart)); + + show_dialog (transient_for, process); +} + +static void +process_table_activate_cb (SysprofProcessesSection *self, + guint position, + GtkColumnView *column_view) +{ + g_autoptr(SysprofDocumentProcess) process = NULL; + GtkSelectionModel *model; + GtkNative *transient_for; + + g_assert (SYSPROF_IS_PROCESSES_SECTION (self)); + g_assert (GTK_IS_COLUMN_VIEW (column_view)); + + model = gtk_column_view_get_model (column_view); + process = g_list_model_get_item (G_LIST_MODEL (model), position); + transient_for = gtk_widget_get_native (GTK_WIDGET (self)); + + show_dialog (transient_for, process); +} + +static void +sysprof_processes_section_dispose (GObject *object) +{ + SysprofProcessesSection *self = (SysprofProcessesSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_PROCESSES_SECTION); + + G_OBJECT_CLASS (sysprof_processes_section_parent_class)->dispose (object); +} + +static void +sysprof_processes_section_class_init (SysprofProcessesSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_processes_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-processes-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofProcessesSection, column_view); + gtk_widget_class_bind_template_child (widget_class, SysprofProcessesSection, list_view); + gtk_widget_class_bind_template_child (widget_class, SysprofProcessesSection, time_column); + gtk_widget_class_bind_template_callback (widget_class, activate_layer_item_cb); + gtk_widget_class_bind_template_callback (widget_class, process_table_activate_cb); + gtk_widget_class_bind_template_callback (widget_class, format_number); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_CHART_LAYER); + g_type_ensure (SYSPROF_TYPE_DOCUMENT_PROCESS); + g_type_ensure (SYSPROF_TYPE_SESSION_MODEL); + g_type_ensure (SYSPROF_TYPE_SESSION_MODEL_ITEM); + g_type_ensure (SYSPROF_TYPE_SINGLE_MODEL); + g_type_ensure (SYSPROF_TYPE_TIME_LABEL); + g_type_ensure (SYSPROF_TYPE_TIME_SERIES); + g_type_ensure (SYSPROF_TYPE_TIME_SPAN_LAYER); + g_type_ensure (SYSPROF_TYPE_VALUE_AXIS); +} + +static void +sysprof_processes_section_init (SysprofProcessesSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_column_view_sort_by_column (self->column_view, + self->time_column, + GTK_SORT_ASCENDING); +} diff --git a/src/libsysprof-ui/sysprof-battery-aid.h b/src/sysprof/sysprof-processes-section.h similarity index 70% rename from src/libsysprof-ui/sysprof-battery-aid.h rename to src/sysprof/sysprof-processes-section.h index 563793f1..cbdafba5 100644 --- a/src/libsysprof-ui/sysprof-battery-aid.h +++ b/src/sysprof/sysprof-processes-section.h @@ -1,6 +1,6 @@ -/* sysprof-battery-aid.h +/* sysprof-processes-section.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,13 @@ #pragma once -#include "sysprof-aid.h" +#include "sysprof-section.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_BATTERY_AID (sysprof_battery_aid_get_type()) +#define SYSPROF_TYPE_PROCESSES_SECTION (sysprof_processes_section_get_type()) -G_DECLARE_FINAL_TYPE (SysprofBatteryAid, sysprof_battery_aid, SYSPROF, BATTERY_AID, SysprofAid) - -SysprofAid *sysprof_battery_aid_new (void); +G_DECLARE_FINAL_TYPE (SysprofProcessesSection, sysprof_processes_section, SYSPROF, PROCESSES_SECTION, SysprofSection) G_END_DECLS + diff --git a/src/sysprof/sysprof-processes-section.ui b/src/sysprof/sysprof-processes-section.ui new file mode 100644 index 00000000..95cc58d5 --- /dev/null +++ b/src/sysprof/sysprof-processes-section.ui @@ -0,0 +1,364 @@ + + + + diff --git a/src/sysprof/sysprof-progress-cell-private.h b/src/sysprof/sysprof-progress-cell-private.h new file mode 100644 index 00000000..ce9406a7 --- /dev/null +++ b/src/sysprof/sysprof-progress-cell-private.h @@ -0,0 +1,36 @@ +/* sysprof-progress-cell.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_PROGRESS_CELL (sysprof_progress_cell_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofProgressCell, sysprof_progress_cell, SYSPROF, PROGRESS_CELL, GtkWidget) + +GtkWidget *sysprof_progress_cell_new (void); +double sysprof_progress_cell_get_fraction (SysprofProgressCell *self); +void sysprof_progress_cell_set_fraction (SysprofProgressCell *self, + double fraction); + +G_END_DECLS diff --git a/src/sysprof/sysprof-progress-cell.c b/src/sysprof/sysprof-progress-cell.c new file mode 100644 index 00000000..51b399b3 --- /dev/null +++ b/src/sysprof/sysprof-progress-cell.c @@ -0,0 +1,296 @@ +/* sysprof-progress-cell.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-css-private.h" +#include "sysprof-progress-cell-private.h" + +struct _SysprofProgressCell +{ + GtkWidget parent_instance; + + AdwBin *trough; + AdwBin *progress; + GtkLabel *label; + GtkLabel *alt_label; + + double fraction; +}; + +enum { + PROP_0, + PROP_FRACTION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofProgressCell, sysprof_progress_cell, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_progress_cell_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + SysprofProgressCell *self = (SysprofProgressCell *)widget; + GtkAllocation all = {0, 0, width, height}; + + g_assert (SYSPROF_IS_PROGRESS_CELL (self)); + + gtk_widget_size_allocate (GTK_WIDGET (self->label), &all, baseline); + gtk_widget_size_allocate (GTK_WIDGET (self->alt_label), &all, baseline); + gtk_widget_size_allocate (GTK_WIDGET (self->trough), &all, baseline); + + if (gtk_widget_get_visible (GTK_WIDGET (self->progress))) + gtk_widget_size_allocate (GTK_WIDGET (self->progress), + &(const GtkAllocation) {0, 0, MAX (1, width*self->fraction), height}, + baseline); +} + +static void +sysprof_progress_cell_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofProgressCell *self = (SysprofProgressCell *)widget; + int w, h; + + g_assert (SYSPROF_IS_PROGRESS_CELL (self)); + + gtk_widget_snapshot_child (widget, GTK_WIDGET (self->trough), snapshot); + gtk_widget_snapshot_child (widget, GTK_WIDGET (self->label), snapshot); + gtk_widget_snapshot_child (widget, GTK_WIDGET (self->progress), snapshot); + + w = gtk_widget_get_width (widget); + h = gtk_widget_get_height (widget); + + gtk_snapshot_push_clip (snapshot, + &GRAPHENE_RECT_INIT (0, 0, w * self->fraction, h)); + gtk_widget_snapshot_child (widget, GTK_WIDGET (self->alt_label), snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +sysprof_progress_cell_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + SysprofProgressCell *self = SYSPROF_PROGRESS_CELL (widget); + GtkWidget *widgets[] = { + GTK_WIDGET (self->trough), + GTK_WIDGET (self->progress), + GTK_WIDGET (self->label), + GTK_WIDGET (self->alt_label), + }; + + *minimum = 0; + *natural = 0; + *minimum_baseline = -1; + *natural_baseline = -1; + + for (guint i = 0; i < G_N_ELEMENTS (widgets); i++) + { + int child_min, child_nat; + + gtk_widget_measure (widgets[i], orientation, for_size, &child_min, &child_nat, NULL, NULL); + + if (child_min > *minimum) + *minimum = child_min; + + if (child_nat > *natural) + *natural = child_nat; + } +} + +static void +sysprof_progress_cell_dispose (GObject *object) +{ + SysprofProgressCell *self = (SysprofProgressCell *)object; + + gtk_widget_unparent (GTK_WIDGET (self->trough)); + gtk_widget_unparent (GTK_WIDGET (self->progress)); + gtk_widget_unparent (GTK_WIDGET (self->label)); + gtk_widget_unparent (GTK_WIDGET (self->alt_label)); + + self->trough = NULL; + self->progress = NULL; + self->label = NULL; + self->alt_label = NULL; + + G_OBJECT_CLASS (sysprof_progress_cell_parent_class)->dispose (object); +} + +static void +sysprof_progress_cell_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofProgressCell *self = SYSPROF_PROGRESS_CELL (object); + + switch (prop_id) + { + case PROP_FRACTION: + g_value_set_double (value, sysprof_progress_cell_get_fraction (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_progress_cell_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofProgressCell *self = SYSPROF_PROGRESS_CELL (object); + + switch (prop_id) + { + case PROP_FRACTION: + sysprof_progress_cell_set_fraction (self, g_value_get_double (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_progress_cell_class_init (SysprofProgressCellClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_progress_cell_dispose; + object_class->get_property = sysprof_progress_cell_get_property; + object_class->set_property = sysprof_progress_cell_set_property; + + widget_class->size_allocate = sysprof_progress_cell_size_allocate; + widget_class->snapshot = sysprof_progress_cell_snapshot; + widget_class->measure = sysprof_progress_cell_measure; + + properties [PROP_FRACTION] = + g_param_spec_double ("fraction", NULL, NULL, + 0, 1, 0, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "progresscell"); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_PROGRESS_BAR); +} + +static void +sysprof_progress_cell_init (SysprofProgressCell *self) +{ + char percent[32]; + + _sysprof_css_init (); + + g_snprintf (percent, sizeof percent, "%6.2lf%%", .0); + + self->label = g_object_new (GTK_TYPE_LABEL, + "xalign", 1.f, + "valign", GTK_ALIGN_CENTER, + "label", percent, + "width-chars", 7, + NULL); + self->alt_label = g_object_new (GTK_TYPE_LABEL, + "xalign", 1.f, + "valign", GTK_ALIGN_CENTER, + "label", percent, + "width-chars", 7, + NULL); + gtk_widget_add_css_class (GTK_WIDGET (self->alt_label), "in-progress"); + self->trough = g_object_new (ADW_TYPE_BIN, + "css-name", "trough", + NULL); + self->progress = g_object_new (ADW_TYPE_BIN, + "css-name", "progress", + "visible", FALSE, + NULL); + + gtk_widget_set_parent (GTK_WIDGET (self->trough), GTK_WIDGET (self)); + gtk_widget_set_parent (GTK_WIDGET (self->label), GTK_WIDGET (self)); + gtk_widget_set_parent (GTK_WIDGET (self->progress), GTK_WIDGET (self)); + gtk_widget_set_parent (GTK_WIDGET (self->alt_label), GTK_WIDGET (self)); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 0.0, + -1); +} + +GtkWidget * +sysprof_progress_cell_new (void) +{ + return g_object_new (SYSPROF_TYPE_PROGRESS_CELL, NULL); +} + +double +sysprof_progress_cell_get_fraction (SysprofProgressCell *self) +{ + g_return_val_if_fail (SYSPROF_IS_PROGRESS_CELL (self), .0); + + return self->fraction; +} + +void +sysprof_progress_cell_set_fraction (SysprofProgressCell *self, + double fraction) +{ + g_return_if_fail (SYSPROF_IS_PROGRESS_CELL (self)); + + fraction = CLAMP (fraction, 0., 1.); + + if (fraction != self->fraction) + { + char text[32]; + + self->fraction = fraction; + + g_snprintf (text, sizeof text, "%6.2lf%%", fraction*100.); + gtk_label_set_text (self->label, text); + gtk_label_set_text (self->alt_label, text); + + gtk_widget_set_visible (GTK_WIDGET (self->progress), fraction > .0); + + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0, + GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, fraction, + GTK_ACCESSIBLE_PROPERTY_VALUE_TEXT, text, + -1); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FRACTION]); + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } +} diff --git a/src/sysprof/sysprof-recording-pad.c b/src/sysprof/sysprof-recording-pad.c new file mode 100644 index 00000000..bd3272df --- /dev/null +++ b/src/sysprof/sysprof-recording-pad.c @@ -0,0 +1,239 @@ +/* sysprof-recording-pad.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include +#include + +#include "sysprof-application.h" +#include "sysprof-recording-pad.h" +#include "sysprof-window.h" + +struct _SysprofRecordingPad +{ + AdwWindow parent_instance; + + SysprofRecording *recording; + + GtkButton *stop_button; + + guint closed : 1; +}; + +enum { + PROP_0, + PROP_RECORDING, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofRecordingPad, sysprof_recording_pad, ADW_TYPE_WINDOW) + +static GParamSpec *properties[N_PROPS]; + +static char * +format_event_count (GObject *unused, + gint64 event_count) +{ + /* translators: this expands to the number of events recorded by the profiler as an indicator of progress */ + return g_strdup_printf (_("%"G_GINT64_FORMAT" events"), event_count); +} + +static char * +format_time (GObject *unused, + gint64 duration) +{ + double elapsed = duration / (double)G_USEC_PER_SEC; + int minutes = floor (elapsed / 60); + int seconds = floor (elapsed - (minutes * 60)); + + return g_strdup_printf ("%02u:%02u", minutes, seconds); +} + +static gboolean +sysprof_recording_pad_close_request (GtkWindow *window) +{ + SysprofRecordingPad *self = (SysprofRecordingPad *)window; + + g_assert (SYSPROF_IS_RECORDING_PAD (self)); + g_assert (self->recording != NULL); + + if (!self->closed) + { + g_debug ("User requested to stop recording"); + + self->closed = TRUE; + sysprof_recording_stop_async (self->recording, NULL, NULL, NULL); + } + + return GDK_EVENT_PROPAGATE; +} + +static void +sysprof_recording_pad_wait_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SysprofRecording *recording = (SysprofRecording *)object; + g_autoptr(SysprofRecordingPad) self = user_data; + g_autoptr(GError) error = NULL; + g_autofd int fd = -1; + + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_RECORDING_PAD (self)); + + g_debug ("Recording has indicated completion"); + + g_application_release (G_APPLICATION (SYSPROF_APPLICATION_DEFAULT)); + + if (!sysprof_recording_wait_finish (recording, result, &error)) + { + GtkWidget *dialog; + + dialog = adw_message_dialog_new (NULL, _("Recording Failed"), NULL); + adw_message_dialog_format_body (ADW_MESSAGE_DIALOG (dialog), + _("Sysprof failed to record.\n\n%s"), + error->message); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "close", _("Close")); + gtk_application_add_window (GTK_APPLICATION (SYSPROF_APPLICATION_DEFAULT), + GTK_WINDOW (dialog)); + gtk_window_present (GTK_WINDOW (dialog)); + } + else if (-1 != (fd = sysprof_recording_dup_fd (self->recording))) + { + lseek (fd, 0, SEEK_SET); + sysprof_window_open_fd (SYSPROF_APPLICATION_DEFAULT, fd); + } + + if (!self->closed) + gtk_window_destroy (GTK_WINDOW (self)); +} + +static void +sysprof_recording_pad_constructed (GObject *object) +{ + SysprofRecordingPad *self = (SysprofRecordingPad *)object; + + G_OBJECT_CLASS (sysprof_recording_pad_parent_class)->constructed (object); + + g_application_hold (G_APPLICATION (SYSPROF_APPLICATION_DEFAULT)); + + g_debug ("Waiting for completion of recording"); + + sysprof_recording_wait_async (self->recording, + NULL, + sysprof_recording_pad_wait_cb, + g_object_ref (self)); +} + +static void +sysprof_recording_pad_dispose (GObject *object) +{ + SysprofRecordingPad *self = (SysprofRecordingPad *)object; + + g_clear_object (&self->recording); + + G_OBJECT_CLASS (sysprof_recording_pad_parent_class)->dispose (object); +} + +static void +sysprof_recording_pad_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofRecordingPad *self = SYSPROF_RECORDING_PAD (object); + + switch (prop_id) + { + case PROP_RECORDING: + g_value_set_object (value, self->recording); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_recording_pad_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofRecordingPad *self = SYSPROF_RECORDING_PAD (object); + + switch (prop_id) + { + case PROP_RECORDING: + self->recording = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_recording_pad_class_init (SysprofRecordingPadClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkWindowClass *window_class = GTK_WINDOW_CLASS (klass); + + object_class->constructed = sysprof_recording_pad_constructed; + object_class->dispose = sysprof_recording_pad_dispose; + object_class->get_property = sysprof_recording_pad_get_property; + object_class->set_property = sysprof_recording_pad_set_property; + + window_class->close_request = sysprof_recording_pad_close_request; + + properties [PROP_RECORDING] = + g_param_spec_object ("recording", NULL, NULL, + SYSPROF_TYPE_RECORDING, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-recording-pad.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofRecordingPad, stop_button); + gtk_widget_class_bind_template_callback (widget_class, format_time); + gtk_widget_class_bind_template_callback (widget_class, format_event_count); +} + +static void +sysprof_recording_pad_init (SysprofRecordingPad *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_widget_grab_focus (GTK_WIDGET (self->stop_button)); +} + +GtkWidget * +sysprof_recording_pad_new (SysprofRecording *recording) +{ + g_return_val_if_fail (SYSPROF_IS_RECORDING (recording), NULL); + + return g_object_new (SYSPROF_TYPE_RECORDING_PAD, + "application", SYSPROF_APPLICATION_DEFAULT, + "recording", recording, + NULL); +} diff --git a/src/libsysprof-ui/sysprof-callgraph-aid.h b/src/sysprof/sysprof-recording-pad.h similarity index 67% rename from src/libsysprof-ui/sysprof-callgraph-aid.h rename to src/sysprof/sysprof-recording-pad.h index 3fdde80a..e7080ed2 100644 --- a/src/libsysprof-ui/sysprof-callgraph-aid.h +++ b/src/sysprof/sysprof-recording-pad.h @@ -1,6 +1,6 @@ -/* sysprof-callgraph-aid.h +/* sysprof-recording-pad.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,16 @@ #pragma once -#include "sysprof-aid.h" +#include + +#include G_BEGIN_DECLS -#define SYSPROF_TYPE_CALLGRAPH_AID (sysprof_callgraph_aid_get_type()) +#define SYSPROF_TYPE_RECORDING_PAD (sysprof_recording_pad_get_type()) -G_DECLARE_FINAL_TYPE (SysprofCallgraphAid, sysprof_callgraph_aid, SYSPROF, CALLGRAPH_AID, SysprofAid) +G_DECLARE_FINAL_TYPE (SysprofRecordingPad, sysprof_recording_pad, SYSPROF, RECORDING_PAD, AdwWindow) -SysprofAid *sysprof_callgraph_aid_new (void); +GtkWidget *sysprof_recording_pad_new (SysprofRecording *recording); G_END_DECLS diff --git a/src/sysprof/sysprof-recording-pad.ui b/src/sysprof/sysprof-recording-pad.ui new file mode 100644 index 00000000..830e9c8f --- /dev/null +++ b/src/sysprof/sysprof-recording-pad.ui @@ -0,0 +1,83 @@ + + + + diff --git a/src/sysprof/sysprof-samples-section.c b/src/sysprof/sysprof-samples-section.c new file mode 100644 index 00000000..3cbf54da --- /dev/null +++ b/src/sysprof/sysprof-samples-section.c @@ -0,0 +1,92 @@ +/* sysprof-samples-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart.h" +#include "sysprof-column-layer.h" +#include "sysprof-samples-section.h" +#include "sysprof-session-model-item.h" +#include "sysprof-session-model.h" +#include "sysprof-time-filter-model.h" +#include "sysprof-time-scrubber.h" +#include "sysprof-time-series.h" +#include "sysprof-time-span-layer.h" +#include "sysprof-traceables-utility.h" +#include "sysprof-value-axis.h" +#include "sysprof-weighted-callgraph-view.h" +#include "sysprof-xy-series.h" + +struct _SysprofSamplesSection +{ + SysprofSection parent_instance; + + SysprofWeightedCallgraphView *callgraph_view; +}; + +G_DEFINE_FINAL_TYPE (SysprofSamplesSection, sysprof_samples_section, SYSPROF_TYPE_SECTION) + +static char * +format_number (gpointer unused, + guint number) +{ + if (number == 0) + return NULL; + return g_strdup_printf ("%'u", number); +} + +static void +sysprof_samples_section_dispose (GObject *object) +{ + SysprofSamplesSection *self = (SysprofSamplesSection *)object; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_SAMPLES_SECTION); + + G_OBJECT_CLASS (sysprof_samples_section_parent_class)->dispose (object); +} + +static void +sysprof_samples_section_class_init (SysprofSamplesSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_samples_section_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-samples-section.ui"); + gtk_widget_class_bind_template_child (widget_class, SysprofSamplesSection, callgraph_view); + gtk_widget_class_bind_template_callback (widget_class, format_number); + + g_type_ensure (SYSPROF_TYPE_CHART); + g_type_ensure (SYSPROF_TYPE_XY_SERIES); + g_type_ensure (SYSPROF_TYPE_COLUMN_LAYER); + g_type_ensure (SYSPROF_TYPE_TIME_FILTER_MODEL); + g_type_ensure (SYSPROF_TYPE_TIME_SCRUBBER); + g_type_ensure (SYSPROF_TYPE_TRACEABLES_UTILITY); + g_type_ensure (SYSPROF_TYPE_VALUE_AXIS); + g_type_ensure (SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW); + g_type_ensure (SYSPROF_TYPE_CATEGORY_SUMMARY); +} + +static void +sysprof_samples_section_init (SysprofSamplesSection *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/libsysprof-ui/sysprof-marks-aid.h b/src/sysprof/sysprof-samples-section.h similarity index 71% rename from src/libsysprof-ui/sysprof-marks-aid.h rename to src/sysprof/sysprof-samples-section.h index f201f3aa..320f3216 100644 --- a/src/libsysprof-ui/sysprof-marks-aid.h +++ b/src/sysprof/sysprof-samples-section.h @@ -1,6 +1,6 @@ -/* sysprof-marks-aid.h +/* sysprof-samples-section.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +20,12 @@ #pragma once -#include "sysprof-aid.h" +#include "sysprof-section.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_MARKS_AID (sysprof_marks_aid_get_type()) +#define SYSPROF_TYPE_SAMPLES_SECTION (sysprof_samples_section_get_type()) -G_DECLARE_FINAL_TYPE (SysprofMarksAid, sysprof_marks_aid, SYSPROF, MARKS_AID, SysprofAid) - -SysprofAid *sysprof_marks_aid_new (void); +G_DECLARE_FINAL_TYPE (SysprofSamplesSection, sysprof_samples_section, SYSPROF, SAMPLES_SECTION, SysprofSection) G_END_DECLS diff --git a/src/sysprof/sysprof-samples-section.ui b/src/sysprof/sysprof-samples-section.ui new file mode 100644 index 00000000..e8416292 --- /dev/null +++ b/src/sysprof/sysprof-samples-section.ui @@ -0,0 +1,238 @@ + + + + diff --git a/src/sysprof/sysprof-scheduler-private.h b/src/sysprof/sysprof-scheduler-private.h new file mode 100644 index 00000000..315eb81f --- /dev/null +++ b/src/sysprof/sysprof-scheduler-private.h @@ -0,0 +1,49 @@ +/* sysprof-scheduler-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +typedef gboolean (*SysprofSchedulerCallback) (gint64 deadline, + gpointer user_data); + +gsize sysprof_scheduler_add (SysprofSchedulerCallback callback, + gpointer user_data); +gsize sysprof_scheduler_add_full (SysprofSchedulerCallback callback, + gpointer user_data, + GDestroyNotify notify); +void sysprof_scheduler_remove (gsize handler_id); + +static inline void +sysprof_scheduler_clear (gsize *handler_id_ptr) +{ + gsize val = *handler_id_ptr; + + if (val) + { + *handler_id_ptr = 0; + sysprof_scheduler_remove (val); + } +} + +G_END_DECLS diff --git a/src/sysprof/sysprof-scheduler.c b/src/sysprof/sysprof-scheduler.c new file mode 100644 index 00000000..26021548 --- /dev/null +++ b/src/sysprof/sysprof-scheduler.c @@ -0,0 +1,278 @@ +/* sysprof-scheduler.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-scheduler-private.h" + +/* The goal of this GSource is to let us pile on a bunch of background work but + * only do a small amount of it at a time per-frame cycle. This becomes more + * important when you have multiple documents open all competing to do + * background work and potentially stalling the main loop. + * + * Instead, we only do 1 msec of work at a time and then wait for the next + * frame to come in. + * + * Since we don't have access to the widgets, we need to base this work off the + * shortest delay between frames among the available monitors. Some aliasing is + * still possible depending on per-monitor scan-outs, but we already have that + * issue without this. + */ + +#define _1_MSEC (G_USEC_PER_SEC / 1000L) + +typedef struct _SysprofScheduler +{ + GSource source; + GQueue queue; + gint64 interval; + gsize last_handler_id; +} SysprofScheduler; + +typedef struct _SysprofTask +{ + GList link; + SysprofSchedulerCallback callback; + gpointer user_data; + GDestroyNotify notify; + gint64 ready_time; + gsize id; +} SysprofTask; + +static GSource *the_source; + +static void +sysprof_task_free (SysprofTask *task) +{ + g_assert (task != NULL); + g_assert (task->link.data == (gpointer)task); + g_assert (task->link.next == NULL); + g_assert (task->link.prev == NULL); + g_assert (task->callback != NULL); + + if (task->notify != NULL) + task->notify (task->user_data); + + g_slice_free (SysprofTask, task); +} + +static SysprofTask * +sysprof_task_new (SysprofSchedulerCallback callback, + gpointer user_data, + GDestroyNotify notify) +{ + SysprofTask *task; + + g_return_val_if_fail (callback != NULL, NULL); + + task = g_slice_new0 (SysprofTask); + task->link.data = task; + task->callback = callback; + task->user_data = user_data; + task->notify = notify; + task->ready_time = 0; /* Now */ + task->id = 0; + + return task; +} + +static gint64 +get_interval (SysprofScheduler *self) +{ + if G_UNLIKELY (self->interval == 0) + { + GdkDisplay *display = gdk_display_get_default (); + GListModel *monitors = gdk_display_get_monitors (display); + guint n_items = g_list_model_get_n_items (monitors); + gint64 lowest_interval = 60000; + + for (guint i = 0; i < n_items; i++) + { + GdkMonitor *monitor = g_list_model_get_item (monitors, i); + gint64 interval = gdk_monitor_get_refresh_rate (monitor); + + if (interval != 0 && interval < lowest_interval) + lowest_interval = interval; + + g_object_unref (monitor); + } + + self->interval = (double)G_USEC_PER_SEC / (double)lowest_interval * 1000.0; + } + + return self->interval; +} + +static gboolean +sysprof_scheduler_prepare (GSource *source, + int *timeout) +{ + *timeout = -1; + return FALSE; +} + +static gboolean +sysprof_scheduler_check (GSource *source) +{ + SysprofScheduler *self = (SysprofScheduler *)source; + SysprofTask *task = g_queue_peek_head (&self->queue); + + return task != NULL && task->ready_time <= g_source_get_time (source); +} + +static gboolean +sysprof_scheduler_dispatch (GSource *source, + GSourceFunc source_func, + gpointer user_data) +{ + SysprofScheduler *self = (SysprofScheduler *)source; + gint64 current = g_source_get_time (source); + gint64 deadline = current + _1_MSEC; + gint64 interval = get_interval (self); + + /* Try to process as many items within our quanta if they */ + while (g_get_monotonic_time () < deadline) + { + SysprofTask *task = g_queue_peek_head (&self->queue); + + if (task == NULL) + break; + + g_queue_unlink (&self->queue, &task->link); + + if (task->callback (deadline, task->user_data)) + { + task->ready_time = current + interval; + g_queue_push_tail_link (&self->queue, &task->link); + continue; + } + + sysprof_task_free (task); + } + + if (self->queue.head != NULL) + { + SysprofTask *task = g_queue_peek_head (&self->queue); + g_source_set_ready_time (source, task->ready_time); + return G_SOURCE_CONTINUE; + } + + return G_SOURCE_REMOVE; +} + +static void +sysprof_scheduler_finalize (GSource *source) +{ +#ifndef G_DISABLE_ASSERT + SysprofScheduler *self = (SysprofScheduler *)source; + + g_assert (self->queue.length == 0); + g_assert (self->queue.head == NULL); + g_assert (self->queue.tail == NULL); +#endif + + if (source == the_source) + the_source = NULL; +} + +static GSourceFuncs source_funcs = { + sysprof_scheduler_prepare, + sysprof_scheduler_check, + sysprof_scheduler_dispatch, + sysprof_scheduler_finalize, +}; + +static SysprofScheduler * +get_scheduler (void) +{ + if (the_source == NULL) + { + the_source = g_source_new (&source_funcs, sizeof (SysprofScheduler)); + g_source_set_name (the_source, "SysprofScheduler"); + g_source_set_priority (the_source, G_PRIORITY_LOW); + g_source_set_ready_time (the_source, 0); + g_source_attach (the_source, g_main_context_default ()); + g_source_unref (the_source); + } + + return (SysprofScheduler *)the_source; +} + +gsize +sysprof_scheduler_add (SysprofSchedulerCallback callback, + gpointer user_data) +{ + return sysprof_scheduler_add_full (callback, user_data, NULL); +} + +gsize +sysprof_scheduler_add_full (SysprofSchedulerCallback callback, + gpointer user_data, + GDestroyNotify notify) +{ + SysprofScheduler *self; + SysprofTask *task; + + g_return_val_if_fail (callback != NULL, 0); + + self = get_scheduler (); + task = sysprof_task_new (callback, user_data, notify); + task->id = ++self->last_handler_id; + + /* Request progress immediately */ + g_queue_push_head_link (&self->queue, &task->link); + g_source_set_ready_time ((GSource *)self, g_source_get_time ((GSource *)self)); + + return task->id; +} + +void +sysprof_scheduler_remove (gsize handler_id) +{ + SysprofScheduler *self; + + g_return_if_fail (handler_id != 0); + + self = get_scheduler (); + + for (const GList *iter = self->queue.head; iter != NULL; iter = iter->next) + { + SysprofTask *task = iter->data; + + if (task->id == handler_id) + { + g_queue_unlink (&self->queue, &task->link); + sysprof_task_free (task); + break; + } + } + + if (self->queue.head != NULL) + { + SysprofTask *task = g_queue_peek_head (&self->queue); + g_source_set_ready_time ((GSource *)self, task->ready_time); + } + else + { + g_source_destroy ((GSource *)self); + } +} diff --git a/src/sysprof/sysprof-section.c b/src/sysprof/sysprof-section.c new file mode 100644 index 00000000..92dd33f5 --- /dev/null +++ b/src/sysprof/sysprof-section.c @@ -0,0 +1,338 @@ +/* sysprof-section.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-section.h" +#include "sysprof-window.h" + +typedef struct +{ + char *category; + char *icon_name; + char *indicator; + char *title; + SysprofSession *session; + GtkWidget *utility; +} SysprofSectionPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SysprofSection, sysprof_section, GTK_TYPE_WIDGET) + +enum { + PROP_0, + PROP_SESSION, + PROP_CATEGORY, + PROP_ICON_NAME, + PROP_INDICATOR, + PROP_TITLE, + PROP_UTILITY, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_section_dispose (GObject *object) +{ + SysprofSection *self = (SysprofSection *)object; + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + GtkWidget *child; + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&priv->utility); + g_clear_object (&priv->session); + g_clear_pointer (&priv->title, g_free); + g_clear_pointer (&priv->category, g_free); + g_clear_pointer (&priv->icon_name, g_free); + g_clear_pointer (&priv->indicator, g_free); + + G_OBJECT_CLASS (sysprof_section_parent_class)->dispose (object); +} + +static void +sysprof_section_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSection *self = SYSPROF_SECTION (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_section_get_session (self)); + break; + + case PROP_CATEGORY: + g_value_set_string (value, sysprof_section_get_category (self)); + break; + + case PROP_INDICATOR: + g_value_set_string (value, sysprof_section_get_indicator (self)); + break; + + case PROP_ICON_NAME: + g_value_set_string (value, sysprof_section_get_icon_name (self)); + break; + + case PROP_TITLE: + g_value_set_string (value, sysprof_section_get_title (self)); + break; + + case PROP_UTILITY: + g_value_set_object (value, sysprof_section_get_utility (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_section_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSection *self = SYSPROF_SECTION (object); + + switch (prop_id) + { + case PROP_SESSION: + sysprof_section_set_session (self, g_value_get_object (value)); + break; + + case PROP_CATEGORY: + sysprof_section_set_category (self, g_value_get_string (value)); + break; + + case PROP_ICON_NAME: + sysprof_section_set_icon_name (self, g_value_get_string (value)); + break; + + case PROP_INDICATOR: + sysprof_section_set_indicator (self, g_value_get_string (value)); + break; + + case PROP_TITLE: + sysprof_section_set_title (self, g_value_get_string (value)); + break; + + case PROP_UTILITY: + sysprof_section_set_utility (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_section_class_init (SysprofSectionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_section_dispose; + object_class->get_property = sysprof_section_get_property; + object_class->set_property = sysprof_section_set_property; + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_CATEGORY] = + g_param_spec_string ("category", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_INDICATOR] = + g_param_spec_string ("indicator", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_UTILITY] = + g_param_spec_object ("utility", NULL, NULL, + GTK_TYPE_WIDGET, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +sysprof_section_init (SysprofSection *self) +{ +} + +SysprofSession * +sysprof_section_get_session (SysprofSection *self) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_SECTION (self), NULL); + + return priv->session; +} + +void +sysprof_section_set_session (SysprofSection *self, + SysprofSession *session) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_SECTION (self)); + g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); + + if (g_set_object (&priv->session, session)) + { + if (SYSPROF_SECTION_GET_CLASS (self)->session_set) + SYSPROF_SECTION_GET_CLASS (self)->session_set (self, session); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); + } +} + +const char * +sysprof_section_get_title (SysprofSection *self) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_SECTION (self), NULL); + + return priv->title; +} + +void +sysprof_section_set_title (SysprofSection *self, + const char *title) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_SECTION (self)); + + if (g_set_str (&priv->title, title)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +const char * +sysprof_section_get_category (SysprofSection *self) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_SECTION (self), NULL); + + return priv->category; +} + +void +sysprof_section_set_category (SysprofSection *self, + const char *category) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_SECTION (self)); + + if (g_set_str (&priv->category, category)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CATEGORY]); +} + +const char * +sysprof_section_get_icon_name (SysprofSection *self) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_SECTION (self), NULL); + + return priv->icon_name; +} + +void +sysprof_section_set_icon_name (SysprofSection *self, + const char *icon_name) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_SECTION (self)); + + if (g_set_str (&priv->icon_name, icon_name)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ICON_NAME]); +} + +const char * +sysprof_section_get_indicator (SysprofSection *self) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_SECTION (self), NULL); + + return priv->indicator; +} + +void +sysprof_section_set_indicator (SysprofSection *self, + const char *indicator) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_if_fail (SYSPROF_IS_SECTION (self)); + + if (g_set_str (&priv->indicator, indicator)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INDICATOR]); +} + +GtkWidget * +sysprof_section_get_utility (SysprofSection *self) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + + g_return_val_if_fail (SYSPROF_IS_SECTION (self), NULL); + + return priv->utility; +} + +void +sysprof_section_set_utility (SysprofSection *self, + GtkWidget *utility) +{ + SysprofSectionPrivate *priv = sysprof_section_get_instance_private (self); + g_autoptr(GtkWidget) hold = NULL; + + g_return_if_fail (SYSPROF_IS_SECTION (self)); + g_return_if_fail (!utility || GTK_IS_WIDGET (utility)); + + g_set_object (&hold, priv->utility); + + if (g_set_object (&priv->utility, utility)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UTILITY]); +} diff --git a/src/sysprof/sysprof-section.h b/src/sysprof/sysprof-section.h new file mode 100644 index 00000000..6a0f9376 --- /dev/null +++ b/src/sysprof/sysprof-section.h @@ -0,0 +1,60 @@ +/* sysprof-section.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SECTION (sysprof_section_get_type()) + +G_DECLARE_DERIVABLE_TYPE (SysprofSection, sysprof_section, SYSPROF, SECTION, GtkWidget) + +struct _SysprofSectionClass +{ + GtkWidgetClass parent_class; + + void (*session_set) (SysprofSection *self, + SysprofSession *session); +}; + +SysprofSession *sysprof_section_get_session (SysprofSection *self); +void sysprof_section_set_session (SysprofSection *self, + SysprofSession *session); +const char *sysprof_section_get_category (SysprofSection *self); +void sysprof_section_set_category (SysprofSection *self, + const char *category); +const char *sysprof_section_get_icon_name (SysprofSection *self); +void sysprof_section_set_icon_name (SysprofSection *self, + const char *icon_name); +const char *sysprof_section_get_indicator (SysprofSection *self); +void sysprof_section_set_indicator (SysprofSection *self, + const char *indicator); +const char *sysprof_section_get_title (SysprofSection *self); +void sysprof_section_set_title (SysprofSection *self, + const char *title); +GtkWidget *sysprof_section_get_utility (SysprofSection *self); +void sysprof_section_set_utility (SysprofSection *self, + GtkWidget *utility); + +G_END_DECLS diff --git a/src/sysprof/sysprof-series-private.h b/src/sysprof/sysprof-series-private.h new file mode 100644 index 00000000..212b484a --- /dev/null +++ b/src/sysprof/sysprof-series-private.h @@ -0,0 +1,53 @@ +/* sysprof-series-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_SERIES_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS(obj, SYSPROF_TYPE_SERIES, SysprofSeriesClass) + +struct _SysprofSeries +{ + GObject parent_instance; + char *title; + GListModel *model; + gulong items_changed_handler; +}; + +struct _SysprofSeriesClass +{ + GObjectClass parent_class; + + GType series_item_type; + + gpointer (*get_series_item) (SysprofSeries *self, + guint position, + gpointer item); + void (*items_changed) (SysprofSeries *self, + GListModel *model, + guint position, + guint removed, + guint added); +}; + +G_END_DECLS diff --git a/src/sysprof/sysprof-series.c b/src/sysprof/sysprof-series.c new file mode 100644 index 00000000..ec2b7381 --- /dev/null +++ b/src/sysprof/sysprof-series.c @@ -0,0 +1,273 @@ +/* sysprof-series.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-series-private.h" + +enum { + PROP_0, + PROP_MODEL, + PROP_TITLE, + N_PROPS +}; + +static GType +sysprof_series_get_item_type (GListModel *model) +{ + return SYSPROF_SERIES_GET_CLASS (model)->series_item_type; +} + +static guint +sysprof_series_get_n_items (GListModel *model) +{ + SysprofSeries *self = SYSPROF_SERIES (model); + + if (self->model != NULL) + return g_list_model_get_n_items (self->model); + + return 0; +} + +static gpointer +sysprof_series_get_item (GListModel *model, + guint position) +{ + SysprofSeries *self = SYSPROF_SERIES (model); + gpointer item; + + if (self->model == NULL) + return NULL; + + item = g_list_model_get_item (self->model, position); + + if (SYSPROF_SERIES_GET_CLASS (self)->get_series_item == NULL) + return item; + + return SYSPROF_SERIES_GET_CLASS (self)->get_series_item (self, position, item); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_series_get_item_type; + iface->get_n_items = sysprof_series_get_n_items; + iface->get_item = sysprof_series_get_item; +} + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SysprofSeries, sysprof_series, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_series_items_changed_cb (SysprofSeries *self, + guint position, + guint removed, + guint added, + GListModel *model) +{ + g_assert (SYSPROF_IS_SERIES (self)); + g_assert (G_IS_LIST_MODEL (model)); + + SYSPROF_SERIES_GET_CLASS (self)->items_changed (self, model, position, removed, added); + + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); +} + +static void +sysprof_series_real_items_changed (SysprofSeries *series, + GListModel *model, + guint position, + guint removed, + guint added) +{ +} + +static void +sysprof_series_dispose (GObject *object) +{ + SysprofSeries *self = (SysprofSeries *)object; + + sysprof_series_set_model (self, NULL); + + g_clear_pointer (&self->title, g_free); + + G_OBJECT_CLASS (sysprof_series_parent_class)->dispose (object); +} + +static void +sysprof_series_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSeries *self = SYSPROF_SERIES (object); + + switch (prop_id) + { + case PROP_MODEL: + g_value_set_object (value, sysprof_series_get_model (self)); + break; + + case PROP_TITLE: + g_value_set_string (value, sysprof_series_get_title (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_series_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSeries *self = SYSPROF_SERIES (object); + + switch (prop_id) + { + case PROP_MODEL: + sysprof_series_set_model (self, g_value_get_object (value)); + break; + + case PROP_TITLE: + sysprof_series_set_title (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_series_class_init (SysprofSeriesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_series_dispose; + object_class->get_property = sysprof_series_get_property; + object_class->set_property = sysprof_series_set_property; + + klass->series_item_type = G_TYPE_OBJECT; + klass->items_changed = sysprof_series_real_items_changed; + + properties[PROP_MODEL] = + g_param_spec_object ("model", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_series_init (SysprofSeries *self) +{ +} + +const char * +sysprof_series_get_title (SysprofSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_SERIES (self), NULL); + + return self->title; +} + +void +sysprof_series_set_title (SysprofSeries *self, + const char *title) +{ + g_return_if_fail (SYSPROF_IS_SERIES (self)); + + if (g_set_str (&self->title, title)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]); +} + +/** + * sysprof_series_get_model: + * @self: a #SysprofSeries + * + * Returns: (transfer none) (nullable): a #GListModel + */ +GListModel * +sysprof_series_get_model (SysprofSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_SERIES (self), NULL); + + return self->model; +} + +void +sysprof_series_set_model (SysprofSeries *self, + GListModel *model) +{ + g_return_if_fail (SYSPROF_IS_SERIES (self)); + g_return_if_fail (!model || G_IS_LIST_MODEL (model)); + + if (model == self->model) + return; + + if (model != NULL) + g_object_ref (model); + + if (self->model != NULL) + { + guint old_len = g_list_model_get_n_items (self->model); + + g_clear_signal_handler (&self->items_changed_handler, self->model); + + if (old_len > 0) + { + SYSPROF_SERIES_GET_CLASS (self)->items_changed (self, self->model, 0, old_len, 0); + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_len, 0); + } + + g_clear_object (&self->model); + } + + self->model = model; + + if (model != NULL) + { + guint new_len = g_list_model_get_n_items (model); + + self->items_changed_handler = + g_signal_connect_object (model, + "items-changed", + G_CALLBACK (sysprof_series_items_changed_cb), + self, + G_CONNECT_SWAPPED); + + if (new_len > 0) + { + SYSPROF_SERIES_GET_CLASS (self)->items_changed (self, self->model, 0, 0, new_len); + g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, new_len); + } + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); +} diff --git a/src/sysprof/sysprof-series.h b/src/sysprof/sysprof-series.h new file mode 100644 index 00000000..2a441488 --- /dev/null +++ b/src/sysprof/sysprof-series.h @@ -0,0 +1,47 @@ +/* sysprof-series.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SERIES (sysprof_series_get_type()) +#define SYSPROF_IS_SERIES(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_SERIES)) +#define SYSPROF_SERIES(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_SERIES, SysprofSeries)) +#define SYSPROF_SERIES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_SERIES, SysprofSeriesClass)) + +typedef struct _SysprofSeries SysprofSeries; +typedef struct _SysprofSeriesClass SysprofSeriesClass; + +GType sysprof_series_get_type (void) G_GNUC_CONST; +GListModel *sysprof_series_get_model (SysprofSeries *self); +void sysprof_series_set_model (SysprofSeries *self, + GListModel *model); +const char *sysprof_series_get_title (SysprofSeries *self); +void sysprof_series_set_title (SysprofSeries *self, + const char *title); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofSeries, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-session-model-item.c b/src/sysprof/sysprof-session-model-item.c new file mode 100644 index 00000000..3c09107e --- /dev/null +++ b/src/sysprof/sysprof-session-model-item.c @@ -0,0 +1,155 @@ +/* sysprof-session-model-item.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-session-model-item.h" + +struct _SysprofSessionModelItem +{ + GObject parent_instance; + SysprofSession *session; + GObject *item; +}; + +enum { + PROP_0, + PROP_SESSION, + PROP_ITEM, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofSessionModelItem, sysprof_session_model_item, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_session_model_item_dispose (GObject *object) +{ + SysprofSessionModelItem *self = (SysprofSessionModelItem *)object; + + g_clear_weak_pointer (&self->session); + g_clear_object (&self->item); + + G_OBJECT_CLASS (sysprof_session_model_item_parent_class)->dispose (object); +} + +static void +sysprof_session_model_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSessionModelItem *self = SYSPROF_SESSION_MODEL_ITEM (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_session_model_item_get_session (self)); + break; + + case PROP_ITEM: + g_value_set_object (value, sysprof_session_model_item_get_item (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_model_item_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSessionModelItem *self = SYSPROF_SESSION_MODEL_ITEM (object); + + switch (prop_id) + { + case PROP_SESSION: + g_set_weak_pointer (&self->session, g_value_get_object (value)); + break; + + case PROP_ITEM: + self->item = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_model_item_class_init (SysprofSessionModelItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_session_model_item_dispose; + object_class->get_property = sysprof_session_model_item_get_property; + object_class->set_property = sysprof_session_model_item_set_property; + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_session_model_item_init (SysprofSessionModelItem *self) +{ +} + +/** + * sysprof_session_model_item_get_session: + * @self: a #SysprofSessionModelItem + * + * Returns: (transfer none) (nullable): a #SysprofSession or %NULL + */ +SysprofSession * +sysprof_session_model_item_get_session (SysprofSessionModelItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION_MODEL_ITEM (self), NULL); + + return self->session; +} + +/** + * sysprof_session_model_item_get_item: + * @self: a #SysprofSessionModelItem + * + * Gets the underlying item. + * + * Returns: (transfer none) (nullable) (type GObject): a #GObject or %NULL + */ +gpointer +sysprof_session_model_item_get_item (SysprofSessionModelItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION_MODEL_ITEM (self), NULL); + + return self->item; +} diff --git a/src/libsysprof-ui/sysprof-ui.h b/src/sysprof/sysprof-session-model-item.h similarity index 61% rename from src/libsysprof-ui/sysprof-ui.h rename to src/sysprof/sysprof-session-model-item.h index 02513d51..6b5cdf52 100644 --- a/src/libsysprof-ui/sysprof-ui.h +++ b/src/sysprof/sysprof-session-model-item.h @@ -1,6 +1,6 @@ -/* sysprof-ui.h +/* sysprof-session-model-item.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,22 +20,17 @@ #pragma once -#include #include +#include "sysprof-session.h" + G_BEGIN_DECLS -#define SYSPROF_UI_INSIDE +#define SYSPROF_TYPE_SESSION_MODEL_ITEM (sysprof_session_model_item_get_type()) -# include "sysprof-check.h" -# include "sysprof-display.h" -# include "sysprof-model-filter.h" -# include "sysprof-notebook.h" -# include "sysprof-page.h" -# include "sysprof-process-model-row.h" -# include "sysprof-visualizer-group.h" -# include "sysprof-visualizer.h" +G_DECLARE_FINAL_TYPE (SysprofSessionModelItem, sysprof_session_model_item, SYSPROF, SESSION_MODEL_ITEM, GObject) -#undef SYSPROF_UI_INSIDE +SysprofSession *sysprof_session_model_item_get_session (SysprofSessionModelItem *self); +gpointer sysprof_session_model_item_get_item (SysprofSessionModelItem *self); G_END_DECLS diff --git a/src/sysprof/sysprof-session-model.c b/src/sysprof/sysprof-session-model.c new file mode 100644 index 00000000..be47559b --- /dev/null +++ b/src/sysprof/sysprof-session-model.c @@ -0,0 +1,285 @@ +/* sysprof-session-model.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-session-model.h" +#include "sysprof-session-model-item.h" + +struct _SysprofSessionModel +{ + GObject parent_instance; + GListModel *model; + GSignalGroup *model_signals; + SysprofSession *session; +}; + +enum { + PROP_0, + PROP_MODEL, + PROP_SESSION, + N_PROPS +}; + +static GType +sysprof_session_model_get_item_type (GListModel *model) +{ + return G_TYPE_OBJECT; +} + +static gpointer +sysprof_session_model_get_item (GListModel *model, + guint position) +{ + SysprofSessionModel *self = SYSPROF_SESSION_MODEL (model); + g_autoptr(GObject) item = NULL; + + if (self->model == NULL) + return NULL; + + if ((item = g_list_model_get_item (self->model, position))) + return g_object_new (SYSPROF_TYPE_SESSION_MODEL_ITEM, + "session", self->session, + "item", item, + NULL); + + return NULL; +} + +static guint +sysprof_session_model_get_n_items (GListModel *model) +{ + SysprofSessionModel *self = SYSPROF_SESSION_MODEL (model); + + if (self->model == NULL || self->session == 0) + return 0; + + return g_list_model_get_n_items (self->model); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_session_model_get_item_type; + iface->get_n_items = sysprof_session_model_get_n_items; + iface->get_item = sysprof_session_model_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofSessionModel, sysprof_session_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_session_model_items_changed_cb (SysprofSessionModel *self, + guint position, + guint removed, + guint added, + GListModel *model) +{ + g_assert (SYSPROF_IS_SESSION_MODEL (self)); + + if (self->session == NULL || self->model != model) + return; + + g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); +} + +static void +sysprof_session_model_dispose (GObject *object) +{ + SysprofSessionModel *self = (SysprofSessionModel *)object; + + g_clear_object (&self->model_signals); + g_clear_object (&self->session); + g_clear_object (&self->model); + + G_OBJECT_CLASS (sysprof_session_model_parent_class)->dispose (object); +} + +static void +sysprof_session_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSessionModel *self = SYSPROF_SESSION_MODEL (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_session_model_get_session (self)); + break; + + case PROP_MODEL: + g_value_set_object (value, sysprof_session_model_get_model (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSessionModel *self = SYSPROF_SESSION_MODEL (object); + + switch (prop_id) + { + case PROP_MODEL: + sysprof_session_model_set_model (self, g_value_get_object (value)); + break; + + case PROP_SESSION: + sysprof_session_model_set_session (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_model_class_init (SysprofSessionModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_session_model_dispose; + object_class->get_property = sysprof_session_model_get_property; + object_class->set_property = sysprof_session_model_set_property; + + properties [PROP_MODEL] = + g_param_spec_object ("model", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_session_model_init (SysprofSessionModel *self) +{ + self->model_signals = g_signal_group_new (G_TYPE_LIST_MODEL); + g_signal_group_connect_object (self->model_signals, + "items-changed", + G_CALLBACK (sysprof_session_model_items_changed_cb), + self, + G_CONNECT_SWAPPED); +} + +SysprofSessionModel * +sysprof_session_model_new (SysprofSession *session, + GListModel *model) +{ + g_return_val_if_fail (!session || SYSPROF_IS_SESSION (session), NULL); + g_return_val_if_fail (!model || G_IS_LIST_MODEL (model), NULL); + + return g_object_new (SYSPROF_TYPE_SESSION_MODEL, + "session", session, + "model", model, + NULL); +} + +/** + * sysprof_model_model_get_model: + * @self: a #SysprofSessionModel + * + * Returns: (transfer none) (nullable): a #GListModel or %NULL + */ +GListModel * +sysprof_session_model_get_model (SysprofSessionModel *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION_MODEL (self), NULL); + + return self->model; +} + +void +sysprof_session_model_set_model (SysprofSessionModel *self, + GListModel *model) +{ + guint old_n_items = 0; + guint new_n_items = 0; + + g_return_if_fail (SYSPROF_IS_SESSION_MODEL (self)); + g_return_if_fail (!model || G_IS_LIST_MODEL (model)); + + if (self->model == model) + return; + + if (model) + g_object_ref (model); + + old_n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + g_set_object (&self->model, model); + g_signal_group_set_target (self->model_signals, model); + new_n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + + if (old_n_items || new_n_items) + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_n_items, new_n_items); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); +} + +/** + * sysprof_session_model_get_session: + * @self: a #SysprofSessionModel + * + * Returns: (transfer none) (nullable): a #SysprofSession or %NULL + */ +SysprofSession * +sysprof_session_model_get_session (SysprofSessionModel *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION_MODEL (self), NULL); + + return self->session; +} + +void +sysprof_session_model_set_session (SysprofSessionModel *self, + SysprofSession *session) +{ + guint old_n_items = 0; + guint new_n_items = 0; + + g_return_if_fail (SYSPROF_IS_SESSION_MODEL (self)); + g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); + + if (self->session == session) + return; + + old_n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + g_set_object (&self->session, session); + new_n_items = g_list_model_get_n_items (G_LIST_MODEL (self)); + + if (old_n_items || new_n_items) + g_list_model_items_changed (G_LIST_MODEL (self), 0, old_n_items, new_n_items); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); +} diff --git a/src/sysprof/sysprof-session-model.h b/src/sysprof/sysprof-session-model.h new file mode 100644 index 00000000..cb0053af --- /dev/null +++ b/src/sysprof/sysprof-session-model.h @@ -0,0 +1,42 @@ +/* sysprof-session-model.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SESSION_MODEL (sysprof_session_model_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofSessionModel, sysprof_session_model, SYSPROF, SESSION_MODEL, GObject) + +SysprofSessionModel *sysprof_session_model_new (SysprofSession *session, + GListModel *model); +GListModel *sysprof_session_model_get_model (SysprofSessionModel *self); +void sysprof_session_model_set_model (SysprofSessionModel *self, + GListModel *model); +SysprofSession *sysprof_session_model_get_session (SysprofSessionModel *self); +void sysprof_session_model_set_session (SysprofSessionModel *self, + SysprofSession *session); + +G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-display-private.h b/src/sysprof/sysprof-session-private.h similarity index 76% rename from src/libsysprof-ui/sysprof-display-private.h rename to src/sysprof/sysprof-session-private.h index 2ca6ad64..93960290 100644 --- a/src/libsysprof-ui/sysprof-display-private.h +++ b/src/sysprof/sysprof-session-private.h @@ -1,6 +1,6 @@ -/* sysprof-display-private.h +/* sysprof-session-private.h * - * Copyright 2021 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,10 +20,11 @@ #pragma once -#include "sysprof-display.h" +#include "sysprof-session.h" G_BEGIN_DECLS -void _sysprof_display_destroy (SysprofDisplay *self); +char *_sysprof_session_describe (SysprofSession *self, + gpointer item); G_END_DECLS diff --git a/src/sysprof/sysprof-session.c b/src/sysprof/sysprof-session.c new file mode 100644 index 00000000..4637cb80 --- /dev/null +++ b/src/sysprof/sysprof-session.c @@ -0,0 +1,539 @@ +/* sysprof-session.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-session-private.h" +#include "sysprof-value-axis.h" + +struct _SysprofSession +{ + GObject parent_instance; + + SysprofDocument *document; + GtkEveryFilter *filter; + + SysprofAxis *visible_time_axis; + SysprofAxis *selected_time_axis; + + SysprofTimeSpan document_time; + SysprofTimeSpan selected_time; + SysprofTimeSpan visible_time; + + guint bottom_up : 1; + guint categorize_frames : 1; + guint include_threads : 1; + guint hide_system_libraries : 1; +}; + +enum { + PROP_0, + PROP_BOTTOM_UP, + PROP_DOCUMENT, + PROP_DOCUMENT_TIME, + PROP_FILTER, + PROP_HIDE_SYSTEM_LIBRARIES, + PROP_INCLUDE_THREADS, + PROP_CATEGORIZE_FRAMES, + PROP_SELECTED_TIME, + PROP_SELECTED_TIME_AXIS, + PROP_VISIBLE_TIME, + PROP_VISIBLE_TIME_AXIS, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofSession, sysprof_session, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_session_update_axis (SysprofSession *self) +{ + g_assert (SYSPROF_IS_SESSION (self)); + + sysprof_value_axis_set_min_value (SYSPROF_VALUE_AXIS (self->visible_time_axis), + self->visible_time.begin_nsec); + sysprof_value_axis_set_max_value (SYSPROF_VALUE_AXIS (self->visible_time_axis), + self->visible_time.end_nsec); + + sysprof_value_axis_set_min_value (SYSPROF_VALUE_AXIS (self->selected_time_axis), + self->selected_time.begin_nsec); + sysprof_value_axis_set_max_value (SYSPROF_VALUE_AXIS (self->selected_time_axis), + self->selected_time.end_nsec); +} + +static void +sysprof_session_set_document (SysprofSession *self, + SysprofDocument *document) +{ + const SysprofTimeSpan *time_span; + + g_assert (SYSPROF_IS_SESSION (self)); + g_assert (!document || SYSPROF_IS_DOCUMENT (document)); + + if (!g_set_object (&self->document, document)) + return; + + /* Select/show the entire document time span */ + time_span = sysprof_document_get_time_span (document); + self->selected_time = self->visible_time = self->document_time = *time_span; + sysprof_session_update_axis (self); +} + +static void +sysprof_session_dispose (GObject *object) +{ + SysprofSession *self = (SysprofSession *)object; + + g_clear_object (&self->visible_time_axis); + g_clear_object (&self->selected_time_axis); + g_clear_object (&self->document); + g_clear_object (&self->filter); + + G_OBJECT_CLASS (sysprof_session_parent_class)->dispose (object); +} + +static void +sysprof_session_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSession *self = SYSPROF_SESSION (object); + + switch (prop_id) + { + case PROP_BOTTOM_UP: + g_value_set_boolean (value, self->bottom_up); + break; + + case PROP_CATEGORIZE_FRAMES: + g_value_set_boolean (value, self->categorize_frames); + break; + + case PROP_DOCUMENT: + g_value_set_object (value, sysprof_session_get_document (self)); + break; + + case PROP_DOCUMENT_TIME: + g_value_set_boxed (value, sysprof_session_get_document_time (self)); + break; + + case PROP_FILTER: + g_value_set_object (value, sysprof_session_get_filter (self)); + break; + + case PROP_HIDE_SYSTEM_LIBRARIES: + g_value_set_boolean (value, self->hide_system_libraries); + break; + + case PROP_INCLUDE_THREADS: + g_value_set_boolean (value, self->include_threads); + break; + + case PROP_SELECTED_TIME: + g_value_set_boxed (value, sysprof_session_get_selected_time (self)); + break; + + case PROP_VISIBLE_TIME: + g_value_set_boxed (value, sysprof_session_get_visible_time (self)); + break; + + case PROP_SELECTED_TIME_AXIS: + g_value_set_object (value, sysprof_session_get_selected_time_axis (self)); + break; + + case PROP_VISIBLE_TIME_AXIS: + g_value_set_object (value, sysprof_session_get_visible_time_axis (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSession *self = SYSPROF_SESSION (object); + + switch (prop_id) + { + case PROP_BOTTOM_UP: + self->bottom_up = g_value_get_boolean (value); + break; + + case PROP_CATEGORIZE_FRAMES: + self->categorize_frames = g_value_get_boolean (value); + break; + + case PROP_DOCUMENT: + sysprof_session_set_document (self, g_value_get_object (value)); + break; + + case PROP_HIDE_SYSTEM_LIBRARIES: + self->hide_system_libraries = g_value_get_boolean (value); + break; + + case PROP_INCLUDE_THREADS: + self->include_threads = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_session_class_init (SysprofSessionClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_session_dispose; + object_class->get_property = sysprof_session_get_property; + object_class->set_property = sysprof_session_set_property; + + properties [PROP_BOTTOM_UP] = + g_param_spec_boolean ("bottom-up", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_CATEGORIZE_FRAMES] = + g_param_spec_boolean ("categorize-frames", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DOCUMENT] = + g_param_spec_object ("document", NULL, NULL, + SYSPROF_TYPE_DOCUMENT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DOCUMENT_TIME] = + g_param_spec_boxed ("document-time", NULL, NULL, + SYSPROF_TYPE_TIME_SPAN, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FILTER] = + g_param_spec_object ("filter", NULL, NULL, + GTK_TYPE_FILTER, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_HIDE_SYSTEM_LIBRARIES] = + g_param_spec_boolean ("hide-system-libraries", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_INCLUDE_THREADS] = + g_param_spec_boolean ("include-threads", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SELECTED_TIME] = + g_param_spec_boxed ("selected-time", NULL, NULL, + SYSPROF_TYPE_TIME_SPAN, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_VISIBLE_TIME] = + g_param_spec_boxed ("visible-time", NULL, NULL, + SYSPROF_TYPE_TIME_SPAN, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_SELECTED_TIME_AXIS] = + g_param_spec_object ("selected-time-axis", NULL, NULL, + SYSPROF_TYPE_AXIS, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_VISIBLE_TIME_AXIS] = + g_param_spec_object ("visible-time-axis", NULL, NULL, + SYSPROF_TYPE_AXIS, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_session_init (SysprofSession *self) +{ + self->filter = gtk_every_filter_new (); + self->selected_time_axis = sysprof_value_axis_new (0, 0); + self->visible_time_axis = sysprof_value_axis_new (0, 0); + self->categorize_frames = TRUE; +} + +SysprofSession * +sysprof_session_new (SysprofDocument *document) +{ + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (document), NULL); + + return g_object_new (SYSPROF_TYPE_SESSION, + "document", document, + NULL); +} + +/** + * sysprof_session_get_document: + * @self: a #SysprofSession + * + * Returns: (transfer none): a #SysprofDocument + */ +SysprofDocument * +sysprof_session_get_document (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return self->document; +} + +/** + * sysprof_session_get_filter: + * @self: a #SysprofSession + * + * Gets the filter for the session which can remove #SysprofDocumentFrame + * which are not matching the current selection filters. + * + * Returns: (transfer none) (nullable): a #GtkFilter + */ +GtkFilter * +sysprof_session_get_filter (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return GTK_FILTER (self->filter); +} + +const SysprofTimeSpan * +sysprof_session_get_selected_time (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return &self->selected_time; +} + +const SysprofTimeSpan * +sysprof_session_get_visible_time (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return &self->visible_time; +} + +const SysprofTimeSpan * +sysprof_session_get_document_time (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return &self->document_time; +} + +void +sysprof_session_select_time (SysprofSession *self, + const SysprofTimeSpan *time_span) +{ + SysprofTimeSpan document_time_span; + gboolean emit_for_visible = FALSE; + + g_return_if_fail (SYSPROF_IS_SESSION (self)); + + document_time_span = *sysprof_document_get_time_span (self->document); + + if (time_span == NULL) + time_span = &document_time_span; + + self->selected_time = sysprof_time_span_order (*time_span); + sysprof_time_span_clamp (&self->selected_time, document_time_span); + + if (self->visible_time.begin_nsec > time_span->begin_nsec) + { + self->visible_time.begin_nsec = time_span->begin_nsec; + emit_for_visible = TRUE; + } + + if (self->visible_time.end_nsec < time_span->end_nsec) + { + self->visible_time.end_nsec = time_span->end_nsec; + emit_for_visible = TRUE; + } + + sysprof_session_update_axis (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_TIME]); + + if (emit_for_visible) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VISIBLE_TIME]); +} + +/** + * sysprof_session_get_selected_time_axis: + * @self: a #SysprofSession + * + * Returns: (transfer none) (nullable): a #SysprofAxis + */ +SysprofAxis * +sysprof_session_get_selected_time_axis (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return self->selected_time_axis; +} + +/** + * sysprof_session_get_visible_time_axis: + * @self: a #SysprofSession + * + * Returns: (transfer none) (nullable): a #SysprofAxis + */ +SysprofAxis * +sysprof_session_get_visible_time_axis (SysprofSession *self) +{ + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + return self->visible_time_axis; +} + +void +sysprof_session_zoom_to_selection (SysprofSession *self) +{ + g_return_if_fail (SYSPROF_IS_SESSION (self)); + + if (memcmp (&self->visible_time, &self->selected_time, sizeof self->visible_time) == 0) + return; + + self->visible_time = self->selected_time; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VISIBLE_TIME]); + + sysprof_session_update_axis (self); +} + +static char * +get_time_str (gint64 o) +{ + char str[32]; + + if (o == 0) + g_snprintf (str, sizeof str, "%.3lfs", .0); + else if (o < 1000000) + g_snprintf (str, sizeof str, "%.3lfμs", o/1000.); + else if (o < SYSPROF_NSEC_PER_SEC) + g_snprintf (str, sizeof str, "%.3lfms", o/1000000.); + else + g_snprintf (str, sizeof str, "%.3lfs", o/(double)SYSPROF_NSEC_PER_SEC); + + return g_strdup (str); +} + +static void +append_time_string (GString *str, + const SysprofTimeSpan *span) +{ + g_autofree char *begin_str = NULL; + + g_assert (str != NULL); + g_assert (span != NULL); + + begin_str = get_time_str (span->begin_nsec); + + g_string_append (str, begin_str); + + if (span->begin_nsec != span->end_nsec) + { + g_autofree char *end_str = get_time_str (span->end_nsec - span->begin_nsec); + + g_string_append_printf (str, " (%s)", end_str); + } +} + +char * +_sysprof_session_describe (SysprofSession *self, + gpointer item) +{ + g_autofree char *text = NULL; + + g_return_val_if_fail (SYSPROF_IS_SESSION (self), NULL); + + if (self->document == NULL) + return NULL; + + if (SYSPROF_IS_DOCUMENT_MARK (item)) + { + SysprofDocumentMark *mark = item; + const SysprofTimeSpan *begin = sysprof_document_get_time_span (self->document); + GString *str = g_string_new (NULL); + const char *group = sysprof_document_mark_get_group (mark); + const char *name = sysprof_document_mark_get_name (mark); + const char *message = sysprof_document_mark_get_message (mark); + SysprofTimeSpan span = { + .begin_nsec = sysprof_document_frame_get_time (item), + .end_nsec = sysprof_document_frame_get_time (item) + sysprof_document_mark_get_duration (mark), + }; + + span = sysprof_time_span_relative_to (span, begin->begin_nsec); + + append_time_string (str, &span); + g_string_append_printf (str, ": %s / %s", group, name); + + if (message && message[0]) + g_string_append_printf (str, ": %s", message); + + return g_string_free (str, FALSE); + } + + if (SYSPROF_IS_DOCUMENT_COUNTER_VALUE (item)) + { + SysprofDocumentCounterValue *value = item; + g_autofree char *value_str = sysprof_document_counter_value_format (value); + const SysprofTimeSpan *begin = sysprof_document_get_time_span (self->document); + gint64 t = sysprof_document_counter_value_get_time (value); + GString *str = g_string_new (NULL); + SysprofTimeSpan span = { t, t }; + + span = sysprof_time_span_relative_to (span, begin->begin_nsec); + + append_time_string (str, &span); + g_string_append_printf (str, ": %s", value_str); + + return g_string_free (str, FALSE); + } + + if (SYSPROF_IS_DOCUMENT_LOG (item)) + { + SysprofDocumentLog *log = item; + const SysprofTimeSpan *begin = sysprof_document_get_time_span (self->document); + GString *str = g_string_new (NULL); + const char *domain = sysprof_document_log_get_domain (log); + const char *message = sysprof_document_log_get_message (log); + gint64 t = sysprof_document_frame_get_time (item); + SysprofTimeSpan span = { t, t }; + + span = sysprof_time_span_relative_to (span, begin->begin_nsec); + + append_time_string (str, &span); + g_string_append_printf (str, ": %s: %s", domain, message); + + return g_string_free (str, FALSE); + } + + return NULL; +} diff --git a/src/sysprof/sysprof-session.h b/src/sysprof/sysprof-session.h new file mode 100644 index 00000000..0e7417dd --- /dev/null +++ b/src/sysprof/sysprof-session.h @@ -0,0 +1,48 @@ +/* sysprof-session.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +#include "sysprof-axis.h" +#include "sysprof-time-span.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SESSION (sysprof_session_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofSession, sysprof_session, SYSPROF, SESSION, GObject) + +SysprofSession *sysprof_session_new (SysprofDocument *document); +SysprofDocument *sysprof_session_get_document (SysprofSession *self); +const SysprofTimeSpan *sysprof_session_get_document_time (SysprofSession *self); +GtkFilter *sysprof_session_get_filter (SysprofSession *self); +const SysprofTimeSpan *sysprof_session_get_selected_time (SysprofSession *self); +const SysprofTimeSpan *sysprof_session_get_visible_time (SysprofSession *self); +SysprofAxis *sysprof_session_get_visible_time_axis (SysprofSession *self); +SysprofAxis *sysprof_session_get_selected_time_axis (SysprofSession *self); +void sysprof_session_select_time (SysprofSession *self, + const SysprofTimeSpan *time_span); +void sysprof_session_zoom_to_selection (SysprofSession *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-sidebar.c b/src/sysprof/sysprof-sidebar.c new file mode 100644 index 00000000..8e64ca5e --- /dev/null +++ b/src/sysprof/sysprof-sidebar.c @@ -0,0 +1,270 @@ +/* sysprof-sidebar.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-section.h" +#include "sysprof-sidebar.h" + +struct _SysprofSidebar +{ + GtkWidget parent_instance; + GSignalGroup *stack_signals; + GtkListBox *list_box; +}; + +enum { + PROP_0, + PROP_STACK, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofSidebar, sysprof_sidebar, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static gboolean +string_to_boolean (GBinding *binding, + const GValue *from_value, + GValue *to_value, + gpointer user_data) +{ + g_value_set_boolean (to_value, !!g_value_get_string (from_value)); + return TRUE; +} + +static GtkWidget * +sysprof_sidebar_create_row (gpointer item, + gpointer user_data) +{ + GtkStackPage *page = item; + SysprofSidebar *sidebar = user_data; + SysprofSection *section; + GtkListBoxRow *row; + GtkLabel *indicator; + GtkImage *image; + GtkLabel *label; + GtkBox *box; + + g_assert (GTK_IS_STACK_PAGE (page)); + g_assert (SYSPROF_IS_SIDEBAR (sidebar)); + + section = SYSPROF_SECTION (gtk_stack_page_get_child (page)); + + box = g_object_new (GTK_TYPE_BOX, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "spacing", 6, + NULL); + label = g_object_new (GTK_TYPE_LABEL, + "xalign", .0f, + "label", sysprof_section_get_title (section), + "hexpand", TRUE, + NULL); + image = g_object_new (GTK_TYPE_IMAGE, + "icon-name", sysprof_section_get_icon_name (section), + NULL); + indicator = g_object_new (GTK_TYPE_LABEL, + "css-classes", (const char * const[]) {"indicator", NULL}, + "valign", GTK_ALIGN_CENTER, + NULL); + gtk_box_append (box, GTK_WIDGET (image)); + gtk_box_append (box, GTK_WIDGET (label)); + gtk_box_append (box, GTK_WIDGET (indicator)); + + g_object_bind_property (section, "indicator", + indicator, "label", + G_BINDING_SYNC_CREATE); + g_object_bind_property_full (section, "indicator", + indicator, "visible", + G_BINDING_SYNC_CREATE, + string_to_boolean, NULL, NULL, NULL); + + row = g_object_new (GTK_TYPE_LIST_BOX_ROW, + "child", box, + NULL); + g_object_set_data_full (G_OBJECT (row), "SECTION", g_object_ref (section), g_object_unref); + + return GTK_WIDGET (row); +} + +static void +sysprof_sidebar_bind_cb (SysprofSidebar *self, + GtkStack *stack, + GSignalGroup *group) +{ + GtkSelectionModel *model; + + g_assert (SYSPROF_IS_SIDEBAR (self)); + g_assert (GTK_IS_STACK (stack)); + g_assert (G_IS_SIGNAL_GROUP (group)); + + model = gtk_stack_get_pages (stack); + + gtk_list_box_bind_model (self->list_box, + G_LIST_MODEL (model), + sysprof_sidebar_create_row, + self, + NULL); + + gtk_list_box_select_row (self->list_box, gtk_list_box_get_row_at_index (self->list_box, 0)); +} + +static void +sysprof_sidebar_unbind_cb (SysprofSidebar *self, + GSignalGroup *group) +{ + g_assert (SYSPROF_IS_SIDEBAR (self)); + g_assert (G_IS_SIGNAL_GROUP (group)); + + gtk_list_box_bind_model (self->list_box, NULL, NULL, NULL, NULL); +} + +static void +list_box_row_activated_cb (SysprofSidebar *self, + GtkListBoxRow *row, + GtkListBox *list_box) +{ + SysprofSection *section; + g_autoptr(GtkStack) stack = NULL; + + g_assert (SYSPROF_IS_SIDEBAR (self)); + g_assert (GTK_IS_LIST_BOX_ROW (row)); + g_assert (GTK_IS_LIST_BOX (list_box)); + + if ((section = g_object_get_data (G_OBJECT (row), "SECTION")) && + SYSPROF_IS_SECTION (section) && + (stack = g_signal_group_dup_target (self->stack_signals))) + gtk_stack_set_visible_child (stack, GTK_WIDGET (section)); +} + +static void +sysprof_sidebar_header_func (GtkListBoxRow *row, + GtkListBoxRow *before_row, + gpointer user_data) +{ + SysprofSection *section; + SysprofSection *before_section; + + if (before_row == NULL) + return; + + if ((section = g_object_get_data (G_OBJECT (row), "SECTION")) && + (before_section = g_object_get_data (G_OBJECT (before_row), "SECTION")) && + g_strcmp0 (sysprof_section_get_category (section), + sysprof_section_get_category (before_section)) != 0) + gtk_list_box_row_set_header (row, gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); +} + +static void +sysprof_sidebar_dispose (GObject *object) +{ + SysprofSidebar *self = (SysprofSidebar *)object; + GtkWidget *child; + + g_clear_object (&self->stack_signals); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + G_OBJECT_CLASS (sysprof_sidebar_parent_class)->dispose (object); +} + +static void +sysprof_sidebar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSidebar *self = SYSPROF_SIDEBAR (object); + + switch (prop_id) + { + case PROP_STACK: + g_value_take_object (value, g_signal_group_dup_target (self->stack_signals)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_sidebar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSidebar *self = SYSPROF_SIDEBAR (object); + + switch (prop_id) + { + case PROP_STACK: + g_signal_group_set_target (self->stack_signals, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_sidebar_class_init (SysprofSidebarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_sidebar_dispose; + object_class->get_property = sysprof_sidebar_get_property; + object_class->set_property = sysprof_sidebar_set_property; + + properties[PROP_STACK] = + g_param_spec_object ("stack", NULL, NULL, + GTK_TYPE_STACK, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-sidebar.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_bind_template_child (widget_class, SysprofSidebar, list_box); + gtk_widget_class_bind_template_callback (widget_class, list_box_row_activated_cb); +} + +static void +sysprof_sidebar_init (SysprofSidebar *self) +{ + self->stack_signals = g_signal_group_new (GTK_TYPE_STACK); + g_signal_connect_object (self->stack_signals, + "bind", + G_CALLBACK (sysprof_sidebar_bind_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->stack_signals, + "unbind", + G_CALLBACK (sysprof_sidebar_unbind_cb), + self, + G_CONNECT_SWAPPED); + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_list_box_set_header_func (self->list_box, + sysprof_sidebar_header_func, + NULL, NULL); +} diff --git a/src/libsysprof-ui/sysprof-logs-page.h b/src/sysprof/sysprof-sidebar.h similarity index 74% rename from src/libsysprof-ui/sysprof-logs-page.h rename to src/sysprof/sysprof-sidebar.h index 97bb2455..60c6f22b 100644 --- a/src/libsysprof-ui/sysprof-logs-page.h +++ b/src/sysprof/sysprof-sidebar.h @@ -1,6 +1,6 @@ -/* sysprof-logs-page.h +/* sysprof-sidebar.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,12 +20,12 @@ #pragma once -#include "sysprof-page.h" +#include G_BEGIN_DECLS -#define SYSPROF_TYPE_LOGS_PAGE (sysprof_logs_page_get_type()) +#define SYSPROF_TYPE_SIDEBAR (sysprof_sidebar_get_type()) -G_DECLARE_FINAL_TYPE (SysprofLogsPage, sysprof_logs_page, SYSPROF, LOGS_PAGE, SysprofPage) +G_DECLARE_FINAL_TYPE (SysprofSidebar, sysprof_sidebar, SYSPROF, SIDEBAR, GtkWidget) G_END_DECLS diff --git a/src/sysprof/sysprof-sidebar.ui b/src/sysprof/sysprof-sidebar.ui new file mode 100644 index 00000000..41c30c59 --- /dev/null +++ b/src/sysprof/sysprof-sidebar.ui @@ -0,0 +1,84 @@ + + + + diff --git a/src/sysprof/sysprof-single-model.c b/src/sysprof/sysprof-single-model.c new file mode 100644 index 00000000..b4312dff --- /dev/null +++ b/src/sysprof/sysprof-single-model.c @@ -0,0 +1,182 @@ +/* sysprof-single-model.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-single-model.h" + +struct _SysprofSingleModel +{ + GObject parent_instance; + GObject *item; +}; + +enum { + PROP_0, + PROP_ITEM, + N_PROPS +}; + +static guint +get_n_items (GListModel *model) +{ + return SYSPROF_SINGLE_MODEL (model)->item ? 1 : 0; +} + +static gpointer +get_item (GListModel *model, + guint position) +{ + if (position == 0 && SYSPROF_SINGLE_MODEL (model)->item) + return g_object_ref (SYSPROF_SINGLE_MODEL (model)->item); + return NULL; +} + +static GType +get_item_type (GListModel *model) +{ + if (SYSPROF_SINGLE_MODEL (model)->item) + return G_OBJECT_TYPE (SYSPROF_SINGLE_MODEL (model)->item); + return G_TYPE_OBJECT; +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_n_items = get_n_items; + iface->get_item = get_item; + iface->get_item_type = get_item_type; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofSingleModel, sysprof_single_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties[N_PROPS]; + +static void +sysprof_single_model_finalize (GObject *object) +{ + SysprofSingleModel *self = (SysprofSingleModel *)object; + + g_clear_object (&self->item); + + G_OBJECT_CLASS (sysprof_single_model_parent_class)->finalize (object); +} + +static void +sysprof_single_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSingleModel *self = SYSPROF_SINGLE_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM: + g_value_set_object (value, sysprof_single_model_get_item (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_single_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSingleModel *self = SYSPROF_SINGLE_MODEL (object); + + switch (prop_id) + { + case PROP_ITEM: + sysprof_single_model_set_item (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_single_model_class_init (SysprofSingleModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_single_model_finalize; + object_class->get_property = sysprof_single_model_get_property; + object_class->set_property = sysprof_single_model_set_property; + + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_single_model_init (SysprofSingleModel *self) +{ +} + +SysprofSingleModel * +sysprof_single_model_new (void) +{ + return g_object_new (SYSPROF_TYPE_SINGLE_MODEL, NULL); +} + +gpointer +sysprof_single_model_get_item (SysprofSingleModel *self) +{ + g_return_val_if_fail (SYSPROF_IS_SINGLE_MODEL (self), NULL); + + return self->item; +} + +void +sysprof_single_model_set_item (SysprofSingleModel *self, + gpointer item) +{ + g_return_if_fail (SYSPROF_IS_SINGLE_MODEL (self)); + g_return_if_fail (!item || G_IS_OBJECT (item)); + + if (self->item == item) + return; + + if (item) + g_object_ref (item); + + if (self->item != NULL) + { + g_clear_object (&self->item); + g_list_model_items_changed (G_LIST_MODEL (self), 0, 1, 0); + } + + self->item = item; + + if (self->item != NULL) + g_list_model_items_changed (G_LIST_MODEL (self), 0, 0, 1); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); +} diff --git a/src/libsysprof/sysprof-proxy-source.h b/src/sysprof/sysprof-single-model.h similarity index 59% rename from src/libsysprof/sysprof-proxy-source.h rename to src/sysprof/sysprof-single-model.h index 284d1bcd..61b58a48 100644 --- a/src/libsysprof/sysprof-proxy-source.h +++ b/src/sysprof/sysprof-single-model.h @@ -1,6 +1,6 @@ -/* sysprof-proxy-source.h +/* sysprof-single-model.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,18 +22,15 @@ #include -#include "sysprof-source.h" - G_BEGIN_DECLS -#define SYSPROF_TYPE_PROXY_SOURCE (sysprof_proxy_source_get_type()) +#define SYSPROF_TYPE_SINGLE_MODEL (sysprof_single_model_get_type()) -SYSPROF_AVAILABLE_IN_ALL -G_DECLARE_FINAL_TYPE (SysprofProxySource, sysprof_proxy_source, SYSPROF, PROXY_SOURCE, GObject) +G_DECLARE_FINAL_TYPE (SysprofSingleModel, sysprof_single_model, SYSPROF, SINGLE_MODEL, GObject) -SYSPROF_AVAILABLE_IN_ALL -SysprofSource *sysprof_proxy_source_new (GBusType bus_type, - const gchar *bus_name, - const gchar *object_path); +SysprofSingleModel *sysprof_single_model_new (void); +gpointer sysprof_single_model_get_item (SysprofSingleModel *self); +void sysprof_single_model_set_item (SysprofSingleModel *self, + gpointer item); G_END_DECLS diff --git a/src/sysprof/sysprof-split-layer.c b/src/sysprof/sysprof-split-layer.c new file mode 100644 index 00000000..e20d6982 --- /dev/null +++ b/src/sysprof/sysprof-split-layer.c @@ -0,0 +1,261 @@ +/* + * sysprof-split-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-split-layer.h" + +struct _SysprofSplitLayer +{ + SysprofChartLayer parent_instance; + GtkWidget *top; + GtkWidget *bottom; +}; + +G_DEFINE_FINAL_TYPE (SysprofSplitLayer, sysprof_split_layer, SYSPROF_TYPE_CHART_LAYER) + +enum { + PROP_0, + PROP_BOTTOM, + PROP_TOP, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_split_layer_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + g_assert (SYSPROF_IS_SPLIT_LAYER (widget)); + + *minimum = *natural = 0; + *minimum_baseline = *natural_baseline = -1; + + for (GtkWidget *child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + int m, n; + + gtk_widget_measure (child, orientation, for_size, &m, &n, NULL, NULL); + + if (m > *minimum) + *minimum = m; + + if (n > *natural) + *natural = n; + } + + if (orientation == GTK_ORIENTATION_VERTICAL) + { + *minimum *= 2; + *natural *= 2; + } +} + +static void +sysprof_split_layer_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + SysprofSplitLayer *self = (SysprofSplitLayer *)widget; + + g_assert (SYSPROF_IS_SPLIT_LAYER (self)); + + if (self->top != NULL) + gtk_widget_size_allocate (self->top, + &(GtkAllocation) { 0, 0, width, floor (height / 2.) }, + -1); + + if (self->bottom != NULL) + gtk_widget_size_allocate (self->bottom, + &(GtkAllocation) { 0, ceil (height / 2.), width, floor (height / 2.) }, + -1); +} + +static void +sysprof_split_layer_finalize (GObject *object) +{ + SysprofSplitLayer *self = (SysprofSplitLayer *)object; + + g_clear_pointer (&self->top, gtk_widget_unparent); + g_clear_pointer (&self->bottom, gtk_widget_unparent); + + G_OBJECT_CLASS (sysprof_split_layer_parent_class)->finalize (object); +} + +static void +sysprof_split_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSplitLayer *self = SYSPROF_SPLIT_LAYER (object); + + switch (prop_id) + { + case PROP_TOP: + g_value_set_object (value, sysprof_split_layer_get_top (self)); + break; + + case PROP_BOTTOM: + g_value_set_object (value, sysprof_split_layer_get_bottom (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_split_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSplitLayer *self = SYSPROF_SPLIT_LAYER (object); + + switch (prop_id) + { + case PROP_TOP: + sysprof_split_layer_set_top (self, g_value_get_object (value)); + break; + + case PROP_BOTTOM: + sysprof_split_layer_set_bottom (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_split_layer_class_init (SysprofSplitLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = sysprof_split_layer_finalize; + object_class->get_property = sysprof_split_layer_get_property; + object_class->set_property = sysprof_split_layer_set_property; + + widget_class->measure = sysprof_split_layer_measure; + widget_class->size_allocate = sysprof_split_layer_size_allocate; + + properties [PROP_TOP] = + g_param_spec_object ("top", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BOTTOM] = + g_param_spec_object ("bottom", NULL, NULL, + SYSPROF_TYPE_CHART_LAYER, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_split_layer_init (SysprofSplitLayer *self) +{ +} + +SysprofChartLayer * +sysprof_split_layer_new (void) +{ + return g_object_new (SYSPROF_TYPE_SPLIT_LAYER, NULL); +} + +/** + * sysprof_split_layer_get_top: + * + * Returns: (transfer none) (nullable): a #SysprofChartLayer or %NULL + */ +SysprofChartLayer * +sysprof_split_layer_get_top (SysprofSplitLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_SPLIT_LAYER (self), NULL); + + return SYSPROF_CHART_LAYER (self->top); +} + +void +sysprof_split_layer_set_top (SysprofSplitLayer *self, + SysprofChartLayer *top) +{ + g_return_if_fail (SYSPROF_IS_SPLIT_LAYER (self)); + g_return_if_fail (!top || SYSPROF_IS_CHART_LAYER (top)); + + if (top == SYSPROF_CHART_LAYER (self->top)) + return; + + if (self->top) + gtk_widget_unparent (self->top); + + self->top = GTK_WIDGET (top); + + if (self->top) + gtk_widget_set_parent (self->top, GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TOP]); +} + +/** + * sysprof_split_layer_get_bottom: + * + * Returns: (transfer none) (nullable): a #SysprofChartLayer or %NULL + */ +SysprofChartLayer * +sysprof_split_layer_get_bottom (SysprofSplitLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_SPLIT_LAYER (self), NULL); + + return SYSPROF_CHART_LAYER (self->bottom); +} + +void +sysprof_split_layer_set_bottom (SysprofSplitLayer *self, + SysprofChartLayer *bottom) +{ + g_return_if_fail (SYSPROF_IS_SPLIT_LAYER (self)); + g_return_if_fail (!bottom || SYSPROF_IS_CHART_LAYER (bottom)); + + if (bottom == SYSPROF_CHART_LAYER (self->bottom)) + return; + + if (self->bottom) + gtk_widget_unparent (self->bottom); + + self->bottom = GTK_WIDGET (bottom); + + if (self->bottom) + gtk_widget_set_parent (self->bottom, GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BOTTOM]); +} diff --git a/src/sysprof/sysprof-split-layer.h b/src/sysprof/sysprof-split-layer.h new file mode 100644 index 00000000..3333c63e --- /dev/null +++ b/src/sysprof/sysprof-split-layer.h @@ -0,0 +1,42 @@ +/* + * sysprof-split-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-chart-layer.h" + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SPLIT_LAYER (sysprof_split_layer_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofSplitLayer, sysprof_split_layer, SYSPROF, SPLIT_LAYER, SysprofChartLayer) + +SysprofChartLayer *sysprof_split_layer_new (void); +SysprofChartLayer *sysprof_split_layer_get_bottom (SysprofSplitLayer *self); +void sysprof_split_layer_set_bottom (SysprofSplitLayer *self, + SysprofChartLayer *top); +SysprofChartLayer *sysprof_split_layer_get_top (SysprofSplitLayer *self); +void sysprof_split_layer_set_top (SysprofSplitLayer *self, + SysprofChartLayer *bottom); + +G_END_DECLS diff --git a/src/libsysprof/sysprof-memprof-source.h b/src/sysprof/sysprof-symbol-label-private.h similarity index 60% rename from src/libsysprof/sysprof-memprof-source.h rename to src/sysprof/sysprof-symbol-label-private.h index cfff995a..b5642287 100644 --- a/src/libsysprof/sysprof-memprof-source.h +++ b/src/sysprof/sysprof-symbol-label-private.h @@ -1,6 +1,6 @@ -/* sysprof-memprof-source.h +/* sysprof-symbol-label-private.h * - * Copyright 2020 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,16 +20,19 @@ #pragma once -#include "sysprof-tracefd-source.h" +#include + +#include G_BEGIN_DECLS -#define SYSPROF_TYPE_MEMPROF_SOURCE (sysprof_memprof_source_get_type()) +#define SYSPROF_TYPE_SYMBOL_LABEL (sysprof_symbol_label_get_type()) -SYSPROF_AVAILABLE_IN_3_36 -G_DECLARE_FINAL_TYPE (SysprofMemprofSource, sysprof_memprof_source, SYSPROF, MEMPROF_SOURCE, GObject) +G_DECLARE_FINAL_TYPE (SysprofSymbolLabel, sysprof_symbol_label, SYSPROF, SYMBOL_LABEL, GtkWidget) -SYSPROF_AVAILABLE_IN_3_36 -SysprofSource *sysprof_memprof_source_new (void); +GtkWidget *sysprof_symbol_label_new (void); +SysprofSymbol *sysprof_symbol_label_get_symbol (SysprofSymbolLabel *self); +void sysprof_symbol_label_set_symbol (SysprofSymbolLabel *self, + SysprofSymbol *symbol); G_END_DECLS diff --git a/src/sysprof/sysprof-symbol-label.c b/src/sysprof/sysprof-symbol-label.c new file mode 100644 index 00000000..812b6fa6 --- /dev/null +++ b/src/sysprof/sysprof-symbol-label.c @@ -0,0 +1,197 @@ +/* sysprof-symbol-label.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-symbol-label-private.h" + +#include "sysprof-symbol-private.h" + +#define CSS_CLASS_PROCESS "process" +#define CSS_CLASS_ROOT "root" +#define CSS_CLASS_CONTEXT_SWITCH "context-switch" + +struct _SysprofSymbolLabel +{ + GtkWidget parent_instance; + GtkWidget *text; + SysprofSymbol *symbol; +}; + +enum { + PROP_0, + PROP_SYMBOL, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofSymbolLabel, sysprof_symbol_label, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; +static const char *kind_classes[] = { + NULL, + "root", + "process", + "thread", + "context-switch", + "user", + "kernel", + "unwindable", +}; + +static void +sysprof_symbol_label_dispose (GObject *object) +{ + SysprofSymbolLabel *self = (SysprofSymbolLabel *)object; + + g_clear_pointer (&self->text, gtk_widget_unparent); + g_clear_object (&self->symbol); + + G_OBJECT_CLASS (sysprof_symbol_label_parent_class)->dispose (object); +} + +static void +sysprof_symbol_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofSymbolLabel *self = SYSPROF_SYMBOL_LABEL (object); + + switch (prop_id) + { + case PROP_SYMBOL: + g_value_set_object (value, sysprof_symbol_label_get_symbol (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_symbol_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofSymbolLabel *self = SYSPROF_SYMBOL_LABEL (object); + + switch (prop_id) + { + case PROP_SYMBOL: + sysprof_symbol_label_set_symbol (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_symbol_label_class_init (SysprofSymbolLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_symbol_label_dispose; + object_class->get_property = sysprof_symbol_label_get_property; + object_class->set_property = sysprof_symbol_label_set_property; + + properties [PROP_SYMBOL] = + g_param_spec_object ("symbol", NULL, NULL, + SYSPROF_TYPE_SYMBOL, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "symbol"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); +} + +static void +sysprof_symbol_label_init (SysprofSymbolLabel *self) +{ + self->text = g_object_new (GTK_TYPE_INSCRIPTION, + "xalign", .0f, + "text-overflow", GTK_INSCRIPTION_OVERFLOW_CLIP, + NULL); + gtk_widget_set_parent (GTK_WIDGET (self->text), GTK_WIDGET (self)); +} + +GtkWidget * +sysprof_symbol_label_new (void) +{ + return g_object_new (SYSPROF_TYPE_SYMBOL_LABEL, NULL); +} + +/** + * sysprof_symbol_label_get_label: + * @self: a #SysprofSymbolLabel + * + * Returns: (transfer none) (nullable): a #SysprofSymbol + */ +SysprofSymbol * +sysprof_symbol_label_get_symbol (SysprofSymbolLabel *self) +{ + g_return_val_if_fail (SYSPROF_IS_SYMBOL_LABEL (self), NULL); + + return self->symbol; +} + +void +sysprof_symbol_label_set_symbol (SysprofSymbolLabel *self, + SysprofSymbol *symbol) +{ + SysprofSymbolKind old_kind; + SysprofSymbolKind new_kind; + const char *name; + + g_return_if_fail (SYSPROF_IS_SYMBOL_LABEL (self)); + g_return_if_fail (!symbol || SYSPROF_IS_SYMBOL (symbol)); + + if (self->symbol == symbol) + return; + + if (self->symbol && symbol && _sysprof_symbol_equal (self->symbol, symbol)) + return; + + old_kind = sysprof_symbol_get_kind (self->symbol); + new_kind = sysprof_symbol_get_kind (symbol); + + g_set_object (&self->symbol, symbol); + + if (old_kind != new_kind) + { + if (old_kind > 0) + gtk_widget_remove_css_class (GTK_WIDGET (self), kind_classes[old_kind]); + + if (new_kind > 0) + gtk_widget_add_css_class (GTK_WIDGET (self), kind_classes[new_kind]); + } + + if (symbol != NULL) + name = sysprof_symbol_get_name (symbol); + else + name = NULL; + + gtk_inscription_set_text (GTK_INSCRIPTION (self->text), name); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SYMBOL]); +} diff --git a/src/sysprof/sysprof-time-filter-model.c b/src/sysprof/sysprof-time-filter-model.c new file mode 100644 index 00000000..c931041e --- /dev/null +++ b/src/sysprof/sysprof-time-filter-model.c @@ -0,0 +1,472 @@ +/* sysprof-time-filter-model.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-time-filter-model.h" + +struct _SysprofTimeFilterModel +{ + GObject parent_instance; + GtkExpression *expression; + GtkSliceListModel *slice; + SysprofTimeSpan time_span; +}; + +enum { + PROP_0, + PROP_EXPRESSION, + PROP_MODEL, + PROP_TIME_SPAN, + N_PROPS +}; + +static GType +sysprof_time_filter_model_get_item_type (GListModel *model) +{ + return g_list_model_get_item_type (G_LIST_MODEL (SYSPROF_TIME_FILTER_MODEL (model)->slice)); +} + +static guint +sysprof_time_filter_model_get_n_items (GListModel *model) +{ + return g_list_model_get_n_items (G_LIST_MODEL (SYSPROF_TIME_FILTER_MODEL (model)->slice)); +} + +static gpointer +sysprof_time_filter_model_get_item (GListModel *model, + guint position) +{ + return g_list_model_get_item (G_LIST_MODEL (SYSPROF_TIME_FILTER_MODEL (model)->slice), position); +} + +static void +list_model_iface_init (GListModelInterface *iface) +{ + iface->get_item_type = sysprof_time_filter_model_get_item_type; + iface->get_n_items = sysprof_time_filter_model_get_n_items; + iface->get_item = sysprof_time_filter_model_get_item; +} + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofTimeFilterModel, sysprof_time_filter_model, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)) + +static GParamSpec *properties [N_PROPS]; + +static inline gint64 +get_item_time (SysprofTimeFilterModel *self, + GObject *item) +{ + g_auto(GValue) value = G_VALUE_INIT; + + g_value_init (&value, G_TYPE_INT64); + gtk_expression_evaluate (self->expression, item, &value); + return g_value_get_int64 (&value); +} + +static void +sysprof_time_filter_model_finalize (GObject *object) +{ + SysprofTimeFilterModel *self = (SysprofTimeFilterModel *)object; + + g_clear_object (&self->slice); + g_clear_pointer (&self->expression, gtk_expression_unref); + + G_OBJECT_CLASS (sysprof_time_filter_model_parent_class)->finalize (object); +} + +static void +sysprof_time_filter_model_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeFilterModel *self = SYSPROF_TIME_FILTER_MODEL (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + gtk_value_set_expression (value, sysprof_time_filter_model_get_expression (self)); + break; + + case PROP_MODEL: + g_value_set_object (value, sysprof_time_filter_model_get_model (self)); + break; + + case PROP_TIME_SPAN: + g_value_set_boxed (value, sysprof_time_filter_model_get_time_span (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_filter_model_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTimeFilterModel *self = SYSPROF_TIME_FILTER_MODEL (object); + + switch (prop_id) + { + case PROP_EXPRESSION: + sysprof_time_filter_model_set_expression (self, gtk_value_get_expression (value)); + break; + + case PROP_MODEL: + sysprof_time_filter_model_set_model (self, g_value_get_object (value)); + break; + + case PROP_TIME_SPAN: + sysprof_time_filter_model_set_time_span (self, g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_filter_model_class_init (SysprofTimeFilterModelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_time_filter_model_finalize; + object_class->get_property = sysprof_time_filter_model_get_property; + object_class->set_property = sysprof_time_filter_model_set_property; + + properties[PROP_EXPRESSION] = + gtk_param_spec_expression ("expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MODEL] = + g_param_spec_object ("model", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TIME_SPAN] = + g_param_spec_boxed ("time-span", NULL, NULL, + SYSPROF_TYPE_TIME_SPAN, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_time_filter_model_init (SysprofTimeFilterModel *self) +{ + self->expression = gtk_property_expression_new (SYSPROF_TYPE_DOCUMENT_FRAME, NULL, "time"); + + self->time_span.begin_nsec = G_MININT64; + self->time_span.end_nsec = G_MAXINT64; + + self->slice = gtk_slice_list_model_new (NULL, 0, GTK_INVALID_LIST_POSITION); + g_signal_connect_object (self->slice, + "items-changed", + G_CALLBACK (g_list_model_items_changed), + self, + G_CONNECT_SWAPPED); +} + +static inline void +uint_order (guint *a, + guint *b) +{ + if (*a > *b) + { + guint what_was_a = *a; + *a = *b; + *b = what_was_a; + } +} + +static guint +binary_search_lte (SysprofTimeFilterModel *self, + GListModel *model, + gint64 value) +{ + g_autoptr(GObject) first = NULL; + g_autoptr(GObject) last = NULL; + guint lo; + guint hi; + guint n_items; + + g_assert (G_IS_LIST_MODEL (model)); + g_assert (g_list_model_get_n_items (model) > 0); + + n_items = g_list_model_get_n_items (model); + first = g_list_model_get_item (model, 0); + last = g_list_model_get_item (model, n_items - 1); + + if (value < get_item_time (self, first)) + return GTK_INVALID_LIST_POSITION; + else if (value == get_item_time (self, first)) + return 0; + else if (value >= get_item_time (self, last)) + return n_items - 1; + + g_clear_object (&first); + g_clear_object (&last); + + lo = 0; + hi = n_items - 1; + + while (lo <= hi) + { + guint mid = lo + (hi - lo) / 2; + g_autoptr(GObject) item = g_list_model_get_item (model, mid); + + if (value < get_item_time (self, item)) + hi = mid - 1; + else if (value > get_item_time (self, item)) + lo = mid + 1; + else + return mid; + } + + uint_order (&lo, &hi); + + first = g_list_model_get_item (model, lo); + last = g_list_model_get_item (model, hi); + + if (value < get_item_time (self, first)) + return lo - 1; + else if (value > get_item_time (self, first)) + return lo; + else if (value < get_item_time (self, last)) + return hi; + + return GTK_INVALID_LIST_POSITION; +} + +static guint +binary_search_gte (SysprofTimeFilterModel *self, + GListModel *model, + gint64 value) +{ + g_autoptr(GObject) first = NULL; + g_autoptr(GObject) last = NULL; + guint lo; + guint hi; + guint n_items; + + g_assert (G_IS_LIST_MODEL (model)); + g_assert (g_list_model_get_n_items (model) > 0); + + n_items = g_list_model_get_n_items (model); + first = g_list_model_get_item (model, 0); + last = g_list_model_get_item (model, n_items - 1); + + if (value <= get_item_time (self, first)) + return 0; + else if (value == get_item_time (self, last)) + return n_items - 1; + else if (value > get_item_time (self, last)) + return GTK_INVALID_LIST_POSITION; + + g_clear_object (&first); + g_clear_object (&last); + + lo = 0; + hi = n_items - 1; + + while (lo <= hi) + { + guint mid = lo + (hi - lo) / 2; + g_autoptr(GObject) item = g_list_model_get_item (model, mid); + + if (value < get_item_time (self, item)) + hi = mid - 1; + else if (value > get_item_time (self, item)) + lo = mid + 1; + else + return mid; + } + + uint_order (&lo, &hi); + + first = g_list_model_get_item (model, lo); + last = g_list_model_get_item (model, hi); + + if (value > get_item_time (self, first)) + return lo + 1; + else if (value < get_item_time (self, last)) + return hi - 1; + + return GTK_INVALID_LIST_POSITION; +} + +static void +calculate_bounds (SysprofTimeFilterModel *self, + GListModel *model, + const SysprofTimeSpan *time_span, + guint *offset, + guint *size) +{ + guint begin; + guint end; + + g_assert (SYSPROF_IS_TIME_FILTER_MODEL (self)); + g_assert (!model || G_IS_LIST_MODEL (model)); + g_assert (time_span != NULL); + g_assert (offset != NULL); + g_assert (size != NULL); + + *offset = 0; + *size = GTK_INVALID_LIST_POSITION-1; + + if (model == NULL || g_list_model_get_n_items (model) == 0) + return; + + if (time_span->begin_nsec == G_MININT64 && time_span->end_nsec == G_MAXINT64) + return; + + begin = binary_search_gte (self, model, time_span->begin_nsec); + end = binary_search_lte (self, model, time_span->end_nsec); + + if (begin > end || begin == GTK_INVALID_LIST_POSITION) + return; + + *offset = begin; + + if (end != GTK_INVALID_LIST_POSITION) + *size = end - begin + 1; +} + +static void +sysprof_time_filter_model_update (SysprofTimeFilterModel *self) +{ + GListModel *model; + guint offset; + guint size; + + g_assert (SYSPROF_IS_TIME_FILTER_MODEL (self)); + + model = sysprof_time_filter_model_get_model (self); + + calculate_bounds (self, model, &self->time_span, &offset, &size); + + gtk_slice_list_model_set_offset (self->slice, offset); + gtk_slice_list_model_set_size (self->slice, size); +} + +SysprofTimeFilterModel * +sysprof_time_filter_model_new (GListModel *model, + const SysprofTimeSpan *time_span) +{ + SysprofTimeFilterModel *self; + + g_return_val_if_fail (!model || G_IS_LIST_MODEL (model), NULL); + + self = g_object_new (SYSPROF_TYPE_TIME_FILTER_MODEL, + "model", model, + "time-span", time_span, + NULL); + + g_clear_object (&model); + + return self; +} + +GListModel * +sysprof_time_filter_model_get_model (SysprofTimeFilterModel *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_FILTER_MODEL (self), NULL); + + return gtk_slice_list_model_get_model (self->slice); +} + +void +sysprof_time_filter_model_set_model (SysprofTimeFilterModel *self, + GListModel *model) +{ + g_return_if_fail (SYSPROF_IS_TIME_FILTER_MODEL (self)); + g_return_if_fail (!model || G_IS_LIST_MODEL (model)); + + if (model == sysprof_time_filter_model_get_model (self)) + return; + + gtk_slice_list_model_set_model (self->slice, model); + sysprof_time_filter_model_update (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); +} + +const SysprofTimeSpan * +sysprof_time_filter_model_get_time_span (SysprofTimeFilterModel *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_FILTER_MODEL (self), NULL); + + return &self->time_span; +} + +void +sysprof_time_filter_model_set_time_span (SysprofTimeFilterModel *self, + const SysprofTimeSpan *time_span) +{ + SysprofTimeSpan empty = {G_MININT64, G_MAXINT64}; + + g_return_if_fail (SYSPROF_IS_TIME_FILTER_MODEL (self)); + + if (time_span == NULL) + time_span = ∅ + + if (!sysprof_time_span_equal (&self->time_span, time_span)) + { + self->time_span = *time_span; + sysprof_time_filter_model_update (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TIME_SPAN]); + } +} + +GtkExpression * +sysprof_time_filter_model_get_expression (SysprofTimeFilterModel *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_FILTER_MODEL (self), NULL); + + return self->expression; +} + +void +sysprof_time_filter_model_set_expression (SysprofTimeFilterModel *self, + GtkExpression *expression) +{ + g_return_if_fail (SYSPROF_IS_TIME_FILTER_MODEL (self)); + g_return_if_fail (!expression || GTK_IS_EXPRESSION (expression)); + + if (expression == self->expression) + return; + + if (expression) + gtk_expression_ref (expression); + + g_clear_pointer (&self->expression, gtk_expression_unref); + + if (expression == NULL) + self->expression = gtk_property_expression_new (SYSPROF_TYPE_DOCUMENT_FRAME, NULL, "time"); + else + self->expression = expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPRESSION]); +} diff --git a/src/sysprof/sysprof-time-filter-model.h b/src/sysprof/sysprof-time-filter-model.h new file mode 100644 index 00000000..bb1f8902 --- /dev/null +++ b/src/sysprof/sysprof-time-filter-model.h @@ -0,0 +1,45 @@ +/* sysprof-time-filter-model.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_FILTER_MODEL (sysprof_time_filter_model_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTimeFilterModel, sysprof_time_filter_model, SYSPROF, TIME_FILTER_MODEL, GObject) + +SysprofTimeFilterModel *sysprof_time_filter_model_new (GListModel *model, + const SysprofTimeSpan *time_span); +GListModel *sysprof_time_filter_model_get_model (SysprofTimeFilterModel *self); +void sysprof_time_filter_model_set_model (SysprofTimeFilterModel *self, + GListModel *model); +GtkExpression *sysprof_time_filter_model_get_expression (SysprofTimeFilterModel *self); +void sysprof_time_filter_model_set_expression (SysprofTimeFilterModel *self, + GtkExpression *expression); +const SysprofTimeSpan *sysprof_time_filter_model_get_time_span (SysprofTimeFilterModel *self); +void sysprof_time_filter_model_set_time_span (SysprofTimeFilterModel *self, + const SysprofTimeSpan *time_span); + +G_END_DECLS diff --git a/src/sysprof/sysprof-time-label.c b/src/sysprof/sysprof-time-label.c new file mode 100644 index 00000000..9c626c62 --- /dev/null +++ b/src/sysprof/sysprof-time-label.c @@ -0,0 +1,276 @@ +/* sysprof-time-label.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-time-label.h" + +struct _SysprofTimeLabel +{ + GtkWidget parent_instance; + GtkLabel *label; + gint64 t; + guint mode : 1; + guint show_zero : 1; +}; + +enum { + PROP_0, + PROP_DURATION, + PROP_TIME_OFFSET, + PROP_SHOW_ZERO, + N_PROPS +}; + +enum { + MODE_TIME_OFFSET, + MODE_DURATION, +}; + +G_DEFINE_FINAL_TYPE (SysprofTimeLabel, sysprof_time_label, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_time_label_dispose (GObject *object) +{ + SysprofTimeLabel *self = (SysprofTimeLabel *)object; + + if (self->label) + { + gtk_widget_unparent (GTK_WIDGET (self->label)); + self->label = NULL; + } + + G_OBJECT_CLASS (sysprof_time_label_parent_class)->dispose (object); +} + +static void +sysprof_time_label_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeLabel *self = SYSPROF_TIME_LABEL (object); + + switch (prop_id) + { + case PROP_DURATION: + g_value_set_int64 (value, sysprof_time_label_get_duration (self)); + break; + + case PROP_SHOW_ZERO: + g_value_set_boolean (value, sysprof_time_label_get_show_zero (self)); + break; + + case PROP_TIME_OFFSET: + g_value_set_int64 (value, sysprof_time_label_get_time_offset (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_label_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTimeLabel *self = SYSPROF_TIME_LABEL (object); + + switch (prop_id) + { + case PROP_DURATION: + sysprof_time_label_set_duration (self, g_value_get_int64 (value)); + break; + + case PROP_SHOW_ZERO: + sysprof_time_label_set_show_zero (self, g_value_get_boolean (value)); + break; + + case PROP_TIME_OFFSET: + sysprof_time_label_set_time_offset (self, g_value_get_int64 (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_label_class_init (SysprofTimeLabelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_time_label_dispose; + object_class->get_property = sysprof_time_label_get_property; + object_class->set_property = sysprof_time_label_set_property; + + properties [PROP_DURATION] = + g_param_spec_int64 ("duration", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_SHOW_ZERO] = + g_param_spec_boolean ("show-zero", NULL, NULL, + TRUE, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_TIME_OFFSET] = + g_param_spec_int64 ("time-offset", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_css_name (widget_class, "timelabel"); +} + +static void +sysprof_time_label_init (SysprofTimeLabel *self) +{ + static const char *css_classes[] = {"numeric", NULL}; + char str[32]; + + self->show_zero = TRUE; + + g_snprintf (str, sizeof str, "%.3lfs", .0); + + self->label = g_object_new (GTK_TYPE_LABEL, + "css-classes", css_classes, + "xalign", 1.f, + "single-line-mode", TRUE, + "label", str, + NULL); + gtk_widget_set_parent (GTK_WIDGET (self->label), GTK_WIDGET (self)); +} + +static void +sysprof_time_label_set_internal (SysprofTimeLabel *self, + gint64 t, + guint mode) +{ + g_return_if_fail (SYSPROF_IS_TIME_LABEL (self)); + + if (mode == self->mode && t == self->t) + return; + + self->mode = mode; + self->t = t; + + if (mode == MODE_DURATION) + { + char str[32]; + + if (t == 0 && !self->show_zero) + str[0] = 0; + else if (t == 0) + g_snprintf (str, sizeof str, "%.3lfs", .0); + else if (t < 1000000) + g_snprintf (str, sizeof str, "%.3lfμs", t/1000.); + else if (t < SYSPROF_NSEC_PER_SEC) + g_snprintf (str, sizeof str, "%.3lfms", t/1000000.); + else + g_snprintf (str, sizeof str, "%.3lfs", t/(double)SYSPROF_NSEC_PER_SEC); + + gtk_label_set_label (self->label, str); + } + else + { + char str[32]; + g_snprintf (str, sizeof str, "%0.3lfs", t/(double)SYSPROF_NSEC_PER_SEC); + gtk_label_set_label (self->label, str); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DURATION]); + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TIME_OFFSET]); +} + +gint64 +sysprof_time_label_get_duration (SysprofTimeLabel *self) +{ + if (self->mode == MODE_DURATION) + return self->t; + return 0; +} + +void +sysprof_time_label_set_duration (SysprofTimeLabel *self, + gint64 duration) +{ + sysprof_time_label_set_internal (self, duration, MODE_DURATION); +} + +gint64 +sysprof_time_label_get_time_offset (SysprofTimeLabel *self) +{ + if (self->mode == MODE_TIME_OFFSET) + return self->t; + return 0; +} + +void +sysprof_time_label_set_time_offset (SysprofTimeLabel *self, + gint64 time_offset) +{ + sysprof_time_label_set_internal (self, time_offset, MODE_TIME_OFFSET); +} + +gboolean +sysprof_time_label_get_show_zero (SysprofTimeLabel *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_LABEL (self), FALSE); + + return self->show_zero; +} + +void +sysprof_time_label_set_show_zero (SysprofTimeLabel *self, + gboolean show_zero) +{ + g_return_if_fail (SYSPROF_IS_TIME_LABEL (self)); + + show_zero = !!show_zero; + + if (self->show_zero != show_zero) + { + self->show_zero = show_zero; + + if (self->t == 0) + { + char str[32]; + + g_snprintf (str, sizeof str, "%0.3lfs", .0); + + if (show_zero) + gtk_label_set_label (self->label, str); + else + gtk_label_set_label (self->label, NULL); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_ZERO]); + } +} diff --git a/src/sysprof/sysprof-time-label.h b/src/sysprof/sysprof-time-label.h new file mode 100644 index 00000000..4900da62 --- /dev/null +++ b/src/sysprof/sysprof-time-label.h @@ -0,0 +1,42 @@ +/* sysprof-time-label.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_LABEL (sysprof_time_label_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTimeLabel, sysprof_time_label, SYSPROF, TIME_LABEL, GtkWidget) + +GtkWidget *sysprof_time_label_new (void); +gboolean sysprof_time_label_get_show_zero (SysprofTimeLabel *self); +void sysprof_time_label_set_show_zero (SysprofTimeLabel *self, + gboolean show_zero); +gint64 sysprof_time_label_get_duration (SysprofTimeLabel *self); +void sysprof_time_label_set_duration (SysprofTimeLabel *self, + gint64 duration); +gint64 sysprof_time_label_get_time_offset (SysprofTimeLabel *self); +void sysprof_time_label_set_time_offset (SysprofTimeLabel *self, + gint64 time_offset); + +G_END_DECLS diff --git a/src/sysprof/sysprof-time-ruler.c b/src/sysprof/sysprof-time-ruler.c new file mode 100644 index 00000000..63398dc9 --- /dev/null +++ b/src/sysprof/sysprof-time-ruler.c @@ -0,0 +1,372 @@ +/* sysprof-time-ruler.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-time-ruler.h" + +#define GROUP_SIZE 100 + +struct _SysprofTimeRuler +{ + GtkWidget parent_instance; + SysprofSession *session; + GSignalGroup *session_signals; +}; + +enum { + PROP_0, + PROP_SESSION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofTimeRuler, sysprof_time_ruler, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_time_ruler_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofTimeRuler *self = (SysprofTimeRuler *)widget; + const SysprofTimeSpan *visible_time; + const SysprofTimeSpan *document_time; + PangoLayout *layout; + gint64 tick_interval; + gint64 duration; + gint64 rem; + int last_x = G_MININT; + int width; + int height; + GdkRGBA color; + GdkRGBA line_color; + + g_assert (SYSPROF_IS_TIME_RULER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + g_assert (!self->session || SYSPROF_IS_SESSION (self->session)); + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + if (self->session == NULL || width == 0 || height == 0) + return; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + { + GtkStyleContext *style_context = gtk_widget_get_style_context (widget); + gtk_style_context_get_color (style_context, &color); + line_color = color; + line_color.alpha *= .3; + } + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + + document_time = sysprof_session_get_document_time (self->session); + visible_time = sysprof_session_get_visible_time (self->session); + duration = sysprof_time_span_duration (*visible_time); + + layout = gtk_widget_create_pango_layout (widget, NULL); + + if (duration > SYSPROF_NSEC_PER_SEC*60) + tick_interval = SYSPROF_NSEC_PER_SEC*10; + else if (duration > SYSPROF_NSEC_PER_SEC*30) + tick_interval = SYSPROF_NSEC_PER_SEC*5; + else if (duration > SYSPROF_NSEC_PER_SEC*15) + tick_interval = SYSPROF_NSEC_PER_SEC*2.5; + else if (duration > SYSPROF_NSEC_PER_SEC*5) + tick_interval = SYSPROF_NSEC_PER_SEC; + else if (duration > SYSPROF_NSEC_PER_SEC*2.5) + tick_interval = SYSPROF_NSEC_PER_SEC*.5; + else if (duration > SYSPROF_NSEC_PER_SEC) + tick_interval = SYSPROF_NSEC_PER_SEC*.25; + else if (duration > SYSPROF_NSEC_PER_SEC*.5) + tick_interval = SYSPROF_NSEC_PER_SEC*.1; + else if (duration > SYSPROF_NSEC_PER_SEC*.25) + tick_interval = SYSPROF_NSEC_PER_SEC*.05; + else if (duration > SYSPROF_NSEC_PER_SEC*.1) + tick_interval = SYSPROF_NSEC_PER_SEC*.02; + else if (duration > SYSPROF_NSEC_PER_SEC*.05) + tick_interval = SYSPROF_NSEC_PER_SEC*.01; + else if (duration > SYSPROF_NSEC_PER_SEC*.01) + tick_interval = SYSPROF_NSEC_PER_SEC*.005; + else if (duration > SYSPROF_NSEC_PER_SEC*.005) + tick_interval = SYSPROF_NSEC_PER_SEC*.001; + else if (duration > SYSPROF_NSEC_PER_SEC*.001) + tick_interval = SYSPROF_NSEC_PER_SEC*.0005; + else if (duration > SYSPROF_NSEC_PER_SEC*.0005) + tick_interval = SYSPROF_NSEC_PER_SEC*.00001; + else + tick_interval = SYSPROF_NSEC_PER_SEC / 1000000; + + rem = (visible_time->begin_nsec - document_time->begin_nsec) % tick_interval; + + for (gint64 t = visible_time->begin_nsec - rem + tick_interval; + t < visible_time->end_nsec; + t += tick_interval) + { + gint64 o = t - visible_time->begin_nsec; + gint64 r = t - document_time->begin_nsec; + double x = (o / (double)duration) * width; + int pw, ph; + char str[32]; + + if (x >= 0 && (x - 6) >= last_x) + { + if (r == 0) + g_snprintf (str, sizeof str, "%.3lfs", .0); + else if (r < 1000000) + g_snprintf (str, sizeof str, "%.3lfμs", r/1000.); + else if (r < SYSPROF_NSEC_PER_SEC) + g_snprintf (str, sizeof str, "%.3lfms", r/1000000.); + else if (duration < SYSPROF_NSEC_PER_SEC/1000) + g_snprintf (str, sizeof str, "%.6lfs", r/(double)SYSPROF_NSEC_PER_SEC); + else if (duration < SYSPROF_NSEC_PER_SEC/100) + g_snprintf (str, sizeof str, "%.5lfs", r/(double)SYSPROF_NSEC_PER_SEC); + else if (duration < SYSPROF_NSEC_PER_SEC/10) + g_snprintf (str, sizeof str, "%.4lfs", r/(double)SYSPROF_NSEC_PER_SEC); + else + g_snprintf (str, sizeof str, "%.3lfs", r/(double)SYSPROF_NSEC_PER_SEC); + + pango_layout_set_text (layout, str, -1); + pango_layout_get_pixel_size (layout, &pw, &ph); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x-pw-6, (height-ph)/2)); + gtk_snapshot_append_layout (snapshot, layout, &color); + gtk_snapshot_restore (snapshot); + + gtk_snapshot_append_color (snapshot, + &line_color, + &GRAPHENE_RECT_INIT (x, 0, 1, height)); + + last_x = x + pw + 6; + } + } + + g_object_unref (layout); +} + +static void +sysprof_time_ruler_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GTK_WIDGET_CLASS (sysprof_time_ruler_parent_class)->measure (widget, + orientation, + for_size, + minimum, + natural, + minimum_baseline, + natural_baseline); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + *minimum = MAX (*minimum, GROUP_SIZE); + *natural = MAX (*natural, *minimum); + } +} + +static void +sysprof_time_ruler_dispose (GObject *object) +{ + SysprofTimeRuler *self = (SysprofTimeRuler *)object; + + g_signal_group_set_target (self->session_signals, NULL); + g_clear_object (&self->session); + + G_OBJECT_CLASS (sysprof_time_ruler_parent_class)->dispose (object); +} + +static void +sysprof_time_ruler_finalize (GObject *object) +{ + SysprofTimeRuler *self = (SysprofTimeRuler *)object; + + g_clear_object (&self->session_signals); + + G_OBJECT_CLASS (sysprof_time_ruler_parent_class)->finalize (object); +} + +static void +sysprof_time_ruler_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeRuler *self = SYSPROF_TIME_RULER (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_time_ruler_get_session (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_ruler_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTimeRuler *self = SYSPROF_TIME_RULER (object); + + switch (prop_id) + { + case PROP_SESSION: + sysprof_time_ruler_set_session (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_ruler_class_init (SysprofTimeRulerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_time_ruler_dispose; + object_class->finalize = sysprof_time_ruler_finalize; + object_class->get_property = sysprof_time_ruler_get_property; + object_class->set_property = sysprof_time_ruler_set_property; + + widget_class->snapshot = sysprof_time_ruler_snapshot; + widget_class->measure = sysprof_time_ruler_measure; + + properties [PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_css_name (widget_class, "timeruler"); +} + +static void +sysprof_time_ruler_init (SysprofTimeRuler *self) +{ + self->session_signals = g_signal_group_new (SYSPROF_TYPE_SESSION); + g_signal_group_connect_object (self->session_signals, + "notify::selected-time", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->session_signals, + "notify::visible-time", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->session_signals, + "bind", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); +} + +GtkWidget * +sysprof_time_ruler_new (void) +{ + return g_object_new (SYSPROF_TYPE_TIME_RULER, NULL); +} + +/** + * sysprof_time_ruler_get_session: + * @self: a #SysprofTimeRuler + * + * Returns: (transfer none) (nullable): a #SysprofSession or %NULL + */ +SysprofSession * +sysprof_time_ruler_get_session (SysprofTimeRuler *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_RULER (self), NULL); + + return self->session; +} + +void +sysprof_time_ruler_set_session (SysprofTimeRuler *self, + SysprofSession *session) +{ + g_return_if_fail (SYSPROF_IS_TIME_RULER (self)); + + if (g_set_object (&self->session, session)) + { + g_signal_group_set_target (self->session_signals, session); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); + } +} + +char * +sysprof_time_ruler_get_label_at_point (SysprofTimeRuler *self, + double x) +{ + char str[32]; + const SysprofTimeSpan *document_time; + const SysprofTimeSpan *visible; + gint64 duration; + gint64 o; + int width; + + g_return_val_if_fail (SYSPROF_IS_TIME_RULER (self), NULL); + + if (x < 0) + return NULL; + + if (x > gtk_widget_get_width (GTK_WIDGET (self))) + return NULL; + + if (self->session == NULL) + return NULL; + + if (!(document_time = sysprof_session_get_document_time (self->session)) || + !(visible = sysprof_session_get_visible_time (self->session)) || + !(duration = sysprof_time_span_duration (*visible))) + return NULL; + + width = gtk_widget_get_width (GTK_WIDGET (self)); + o = visible->begin_nsec + (x / (double)width * duration) - document_time->begin_nsec; + + if (o == 0) + g_snprintf (str, sizeof str, "%.3lfs", .0); + else if (o < 1000000) + g_snprintf (str, sizeof str, "%.3lfμs", o/1000.); + else if (o < SYSPROF_NSEC_PER_SEC) + g_snprintf (str, sizeof str, "%.3lfms", o/1000000.); + else if (duration < SYSPROF_NSEC_PER_SEC/1000) + g_snprintf (str, sizeof str, "%.6lfs", o/(double)SYSPROF_NSEC_PER_SEC); + else if (duration < SYSPROF_NSEC_PER_SEC/100) + g_snprintf (str, sizeof str, "%.5lfs", o/(double)SYSPROF_NSEC_PER_SEC); + else if (duration < SYSPROF_NSEC_PER_SEC/10) + g_snprintf (str, sizeof str, "%.4lfs", o/(double)SYSPROF_NSEC_PER_SEC); + else + g_snprintf (str, sizeof str, "%.3lfs", o/(double)SYSPROF_NSEC_PER_SEC); + + return g_strdup (str); +} diff --git a/src/sysprof/sysprof-time-ruler.h b/src/sysprof/sysprof-time-ruler.h new file mode 100644 index 00000000..62832997 --- /dev/null +++ b/src/sysprof/sysprof-time-ruler.h @@ -0,0 +1,38 @@ +/* sysprof-time-ruler.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-session.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_RULER (sysprof_time_ruler_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTimeRuler, sysprof_time_ruler, SYSPROF, TIME_RULER, GtkWidget) + +GtkWidget *sysprof_time_ruler_new (void); +SysprofSession *sysprof_time_ruler_get_session (SysprofTimeRuler *self); +void sysprof_time_ruler_set_session (SysprofTimeRuler *self, + SysprofSession *session); +char *sysprof_time_ruler_get_label_at_point (SysprofTimeRuler *self, + double x); + +G_END_DECLS diff --git a/src/sysprof/sysprof-time-scrubber.c b/src/sysprof/sysprof-time-scrubber.c new file mode 100644 index 00000000..609a443e --- /dev/null +++ b/src/sysprof/sysprof-time-scrubber.c @@ -0,0 +1,702 @@ +/* sysprof-time-scrubber.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart-layer.h" +#include "sysprof-session-private.h" +#include "sysprof-time-ruler.h" +#include "sysprof-time-scrubber.h" + +struct _SysprofTimeScrubber +{ + GtkWidget parent_instance; + + SysprofSession *session; + GSignalGroup *session_signals; + + GtkLabel *informative; + GtkBox *main; + GtkLabel *timecode; + GtkBox *vbox; + SysprofTimeRuler *ruler; + GtkButton *zoom; + + double motion_x; + double motion_y; + + double drag_start_x; + double drag_start_y; + double drag_offset_x; + double drag_offset_y; + + guint in_drag_selection : 1; +}; + +enum { + PROP_0, + PROP_SESSION, + N_PROPS +}; + +static void buildable_iface_init (GtkBuildableIface *iface); +static GtkBuildableIface *buildable_parent; + +G_DEFINE_FINAL_TYPE_WITH_CODE (SysprofTimeScrubber, sysprof_time_scrubber, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init)) + +static GParamSpec *properties[N_PROPS]; + +static void +set_motion (SysprofTimeScrubber *self, + double x, + double y) +{ + gboolean timecode_visible = FALSE; + gboolean informative_visible = FALSE; + GtkWidget *pick; + int ruler_start; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + if (self->motion_x == x && self->motion_y == y) + return; + + self->motion_x = x; + self->motion_y = y; + + ruler_start = 0; + timecode_visible = x >= ruler_start; + + if (timecode_visible) + { + g_autofree char *str = sysprof_time_ruler_get_label_at_point (self->ruler, x - ruler_start); + gtk_label_set_label (self->timecode, str); + } + + pick = gtk_widget_pick (GTK_WIDGET (self), x, y, 0); + + if (pick != NULL) + { + SysprofChartLayer *layer = SYSPROF_CHART_LAYER (gtk_widget_get_ancestor (pick, SYSPROF_TYPE_CHART_LAYER)); + + if (layer != NULL) + { + g_autoptr(GObject) item = NULL; + g_autofree char *text = NULL; + double layer_x; + double layer_y; + + gtk_widget_translate_coordinates (GTK_WIDGET (self), + GTK_WIDGET (layer), + x, y, &layer_x, &layer_y); + + if ((item = sysprof_chart_layer_lookup_item (layer, layer_x, layer_y))) + text = _sysprof_session_describe (self->session, item); + + gtk_label_set_label (self->informative, text); + + informative_visible = text != NULL; + } + } + + gtk_widget_set_visible (GTK_WIDGET (self->timecode), timecode_visible); + gtk_widget_set_visible (GTK_WIDGET (self->informative), informative_visible); + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_time_scrubber_motion_enter_cb (SysprofTimeScrubber *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + set_motion (self, x, y); +} + +static void +sysprof_time_scrubber_motion_leave_cb (SysprofTimeScrubber *self, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + set_motion (self, -1, -1); +} + +static void +sysprof_time_scrubber_motion_cb (SysprofTimeScrubber *self, + double x, + double y, + GtkEventControllerMotion *motion) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_EVENT_CONTROLLER_MOTION (motion)); + + set_motion (self, x, y); +} + +static void +sysprof_time_scrubber_drag_begin_cb (SysprofTimeScrubber *self, + double start_x, + double start_y, + GtkGestureDrag *drag) +{ + graphene_rect_t zoom_area; + double x, y; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + gtk_widget_translate_coordinates (GTK_WIDGET (self->zoom), + GTK_WIDGET (self), + 0, 0, &x, &y); + zoom_area = GRAPHENE_RECT_INIT (x, y, + gtk_widget_get_width (GTK_WIDGET (self->zoom)), + gtk_widget_get_height (GTK_WIDGET (self->zoom))); + + if (start_x < 0 || + (gtk_widget_get_visible (GTK_WIDGET (self->zoom)) && + graphene_rect_contains_point (&zoom_area, &GRAPHENE_POINT_INIT (start_x, start_y)))) + { + gtk_gesture_set_state (GTK_GESTURE (drag), GTK_EVENT_SEQUENCE_DENIED); + return; + } + + self->drag_start_x = start_x; + self->drag_start_y = start_y; + self->drag_offset_x = 0; + self->drag_offset_y = 0; + + gtk_widget_set_visible (GTK_WIDGET (self->zoom), FALSE); + + self->in_drag_selection = TRUE; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_time_scrubber_drag_end_cb (SysprofTimeScrubber *self, + double offset_x, + double offset_y, + GtkGestureDrag *drag) +{ + int base_x; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + if (self->session == NULL) + goto cleanup; + + base_x = 0; + + if (self->drag_offset_x != .0) + { + graphene_rect_t selection; + graphene_rect_t area; + int width; + int height; + + width = gtk_widget_get_width (GTK_WIDGET (self)) - base_x; + height = gtk_widget_get_height (GTK_WIDGET (self)); + + area = GRAPHENE_RECT_INIT (base_x, 0, width, height); + selection = GRAPHENE_RECT_INIT (self->drag_start_x, + 0, + self->drag_offset_x, + height); + graphene_rect_normalize (&selection); + + if (graphene_rect_intersection (&area, &selection, &selection)) + { + double begin = (selection.origin.x - area.origin.x) / area.size.width; + double end = (selection.origin.x + selection.size.width - area.origin.x) / area.size.width; + const SysprofTimeSpan *visible = sysprof_session_get_visible_time (self->session); + gint64 visible_duration = visible->end_nsec - visible->begin_nsec; + SysprofTimeSpan to_select; + + to_select.begin_nsec = visible->begin_nsec + (begin * visible_duration); + to_select.end_nsec = visible->begin_nsec + (end * visible_duration); + + sysprof_session_select_time (self->session, &to_select); + + gtk_widget_grab_focus (GTK_WIDGET (self->zoom)); + } + } + else if (self->drag_start_x >= base_x) + { + sysprof_session_select_time (self->session, sysprof_session_get_visible_time (self->session)); + } + +cleanup: + self->drag_start_x = -1; + self->drag_start_y = -1; + self->drag_offset_x = 0; + self->drag_offset_y = 0; + + self->in_drag_selection = FALSE; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +sysprof_time_scrubber_drag_update_cb (SysprofTimeScrubber *self, + double offset_x, + double offset_y, + GtkGestureDrag *drag) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_GESTURE_DRAG (drag)); + + self->drag_offset_x = offset_x, + self->drag_offset_y = offset_y; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static gboolean +get_selected_area (SysprofTimeScrubber *self, + graphene_rect_t *area, + graphene_rect_t *selection) +{ + const SysprofTimeSpan *selected; + const SysprofTimeSpan *visible; + SysprofTimeSpan relative; + gint64 time_duration; + double begin; + double end; + int base_x; + int width; + int height; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (area != NULL); + g_assert (selection != NULL); + + if (self->session == NULL) + return FALSE; + + base_x = 0; + width = gtk_widget_get_width (GTK_WIDGET (self)) - base_x; + height = gtk_widget_get_height (GTK_WIDGET (self)); + + *area = GRAPHENE_RECT_INIT (base_x, 0, width, height); + + if (self->in_drag_selection && self->drag_offset_x != .0) + { + *selection = GRAPHENE_RECT_INIT (self->drag_start_x, + 0, + self->drag_offset_x, + height); + graphene_rect_normalize (selection); + return graphene_rect_intersection (area, selection, selection); + } + + /* If selected range == visible range, then there is no selection */ + selected = sysprof_session_get_selected_time (self->session); + visible = sysprof_session_get_visible_time (self->session); + if (memcmp (selected, visible, sizeof *selected) == 0) + return FALSE; + + time_duration = sysprof_time_span_duration (*visible); + relative = sysprof_time_span_relative_to (*selected, visible->begin_nsec); + + begin = relative.begin_nsec / (double)time_duration; + end = relative.end_nsec / (double)time_duration; + + *selection = GRAPHENE_RECT_INIT (area->origin.x + (begin * width), + 0, + (end * width) - (begin * width), + area->size.height); + + return TRUE; +} + +static void +sysprof_time_scrubber_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + gtk_widget_measure (GTK_WIDGET (self->main), + orientation, + for_size, + minimum, + natural, + minimum_baseline, + natural_baseline); +} + +static void +sysprof_time_scrubber_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + graphene_rect_t area; + graphene_rect_t selection; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + gtk_widget_size_allocate (GTK_WIDGET (self->main), + &(GtkAllocation) {0, 0, width, height}, + baseline); + + if (get_selected_area (self, &area, &selection)) + { + graphene_point_t middle; + GtkRequisition min_req; + GtkRequisition nat_req; + + /* Position the zoom button in the center of the selected area */ + gtk_widget_get_preferred_size (GTK_WIDGET (self->zoom), &min_req, &nat_req); + graphene_rect_get_center (&selection, &middle); + gtk_widget_size_allocate (GTK_WIDGET (self->zoom), + &(GtkAllocation) { + middle.x - (min_req.width/2), + middle.y - (min_req.height/2), + min_req.width, + min_req.height + }, -1); + } + + if (gtk_widget_get_visible (GTK_WIDGET (self->timecode))) + { + GtkRequisition min_req; + GtkRequisition nat_req; + + gtk_widget_get_preferred_size (GTK_WIDGET (self->timecode), &min_req, &nat_req); + + if (self->motion_x + min_req.width < gtk_widget_get_width (GTK_WIDGET (self))) + gtk_widget_size_allocate (GTK_WIDGET (self->timecode), + &(GtkAllocation) { + self->motion_x, 0, + min_req.width, min_req.height + }, -1); + else + gtk_widget_size_allocate (GTK_WIDGET (self->timecode), + &(GtkAllocation) { + self->motion_x - min_req.width, 0, + min_req.width, min_req.height + }, -1); + } + + if (gtk_widget_get_visible (GTK_WIDGET (self->informative))) + { + GtkRequisition min_req; + GtkRequisition nat_req; + + gtk_widget_get_preferred_size (GTK_WIDGET (self->informative), &min_req, &nat_req); + + if (self->motion_x + min_req.width < gtk_widget_get_width (GTK_WIDGET (self))) + gtk_widget_size_allocate (GTK_WIDGET (self->informative), + &(GtkAllocation) { + self->motion_x, height - min_req.height, + min_req.width, min_req.height + }, -1); + else + gtk_widget_size_allocate (GTK_WIDGET (self->informative), + &(GtkAllocation) { + self->motion_x - min_req.width, height - min_req.height, + min_req.width, min_req.height + }, -1); + } +} + +static void +sysprof_time_scrubber_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + graphene_rect_t area; + graphene_rect_t selection; + GdkRGBA shadow_color; + GdkRGBA line_color; + GdkRGBA color; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->main), snapshot); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + { + GtkStyleContext *style_context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_get_color (style_context, &color); + + shadow_color = color; + shadow_color.alpha *= .1; + + line_color = color; + line_color.alpha *= .5; + } +G_GNUC_END_IGNORE_DEPRECATIONS + + if (get_selected_area (self, &area, &selection)) + { + gtk_snapshot_append_color (snapshot, + &shadow_color, + &GRAPHENE_RECT_INIT (area.origin.x, + area.origin.y, + selection.origin.x - area.origin.x, + area.size.height)); + + gtk_snapshot_append_color (snapshot, + &shadow_color, + &GRAPHENE_RECT_INIT (selection.origin.x + selection.size.width, + area.origin.y, + (area.origin.x + area.size.width) - (selection.origin.x + selection.size.width), + area.size.height)); + } + + if (self->motion_x != -1 && self->motion_y != -1 && self->motion_x > 0) + gtk_snapshot_append_color (snapshot, + &line_color, + &GRAPHENE_RECT_INIT (self->motion_x, 0, 1, + gtk_widget_get_height (GTK_WIDGET (self)))); + + if (gtk_widget_get_visible (GTK_WIDGET (self->zoom))) + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->zoom), snapshot); + + if (gtk_widget_get_visible (GTK_WIDGET (self->timecode))) + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->timecode), snapshot); + + if (gtk_widget_get_visible (GTK_WIDGET (self->informative))) + gtk_widget_snapshot_child (GTK_WIDGET (self), GTK_WIDGET (self->informative), snapshot); +} + +static void +notify_time_cb (SysprofTimeScrubber *self, + GParamSpec *pspec, + SysprofSession *session) +{ + const SysprofTimeSpan *visible; + const SysprofTimeSpan *selected; + gboolean button_visible; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (SYSPROF_IS_SESSION (session)); + + visible = sysprof_session_get_visible_time (session); + selected = sysprof_session_get_selected_time (session); + + button_visible = memcmp (visible, selected, sizeof *visible) != 0; + + gtk_widget_set_visible (GTK_WIDGET (self->zoom), button_visible); +} + +static void +sysprof_time_scrubber_zoom_to_selection (GtkWidget *widget, + const char *action_name, + GVariant *params) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)widget; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + if (self->session != NULL) + sysprof_session_zoom_to_selection (self->session); +} + +static void +sysprof_time_scrubber_dispose (GObject *object) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)object; + GtkWidget *child; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_TIME_SCRUBBER); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); + + g_clear_object (&self->session); + g_clear_object (&self->session_signals); + + G_OBJECT_CLASS (sysprof_time_scrubber_parent_class)->dispose (object); +} + +static void +sysprof_time_scrubber_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeScrubber *self = SYSPROF_TIME_SCRUBBER (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, sysprof_time_scrubber_get_session (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_scrubber_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTimeScrubber *self = SYSPROF_TIME_SCRUBBER (object); + + switch (prop_id) + { + case PROP_SESSION: + sysprof_time_scrubber_set_session (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_scrubber_class_init (SysprofTimeScrubberClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_time_scrubber_dispose; + object_class->get_property = sysprof_time_scrubber_get_property; + object_class->set_property = sysprof_time_scrubber_set_property; + + widget_class->measure = sysprof_time_scrubber_measure; + widget_class->snapshot = sysprof_time_scrubber_snapshot; + widget_class->size_allocate = sysprof_time_scrubber_size_allocate; + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-time-scrubber.ui"); + + gtk_widget_class_set_css_name (widget_class, "timescrubber"); + + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, informative); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, main); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, ruler); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, timecode); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, vbox); + gtk_widget_class_bind_template_child (widget_class, SysprofTimeScrubber, zoom); + + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_motion_enter_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_motion_leave_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_motion_cb); + + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_drag_begin_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_drag_end_cb); + gtk_widget_class_bind_template_callback (widget_class, sysprof_time_scrubber_drag_update_cb); + + gtk_widget_class_install_action (widget_class, "zoom-to-selection", NULL, sysprof_time_scrubber_zoom_to_selection); + + g_type_ensure (SYSPROF_TYPE_TIME_RULER); +} + +static void +sysprof_time_scrubber_init (SysprofTimeScrubber *self) +{ + self->session_signals = g_signal_group_new (SYSPROF_TYPE_SESSION); + g_signal_group_connect_object (self->session_signals, + "notify::selected-time", + G_CALLBACK (notify_time_cb), + self, + G_CONNECT_SWAPPED); + g_signal_group_connect_object (self->session_signals, + "notify::visible-time", + G_CALLBACK (notify_time_cb), + self, + G_CONNECT_SWAPPED); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +SysprofSession * +sysprof_time_scrubber_get_session (SysprofTimeScrubber *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SCRUBBER (self), NULL); + + return self->session; +} + +void +sysprof_time_scrubber_set_session (SysprofTimeScrubber *self, + SysprofSession *session) +{ + g_return_if_fail (SYSPROF_IS_TIME_SCRUBBER (self)); + g_return_if_fail (!session || SYSPROF_IS_SESSION (session)); + + if (g_set_object (&self->session, session)) + { + g_signal_group_set_target (self->session_signals, session); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); + } +} + +void +sysprof_time_scrubber_add_chart (SysprofTimeScrubber *self, + GtkWidget *chart) +{ + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + g_assert (GTK_IS_WIDGET (chart)); + + gtk_box_append (self->vbox, chart); +} + +static void +sysprof_time_scrubber_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *object, + const char *type) +{ + SysprofTimeScrubber *self = (SysprofTimeScrubber *)buildable; + + g_assert (SYSPROF_IS_TIME_SCRUBBER (self)); + + if (g_strcmp0 (type, "chart") == 0) + sysprof_time_scrubber_add_chart (self, GTK_WIDGET (object)); + else + buildable_parent->add_child (buildable, builder, object, type); +} + +static void +buildable_iface_init (GtkBuildableIface *iface) +{ + buildable_parent = g_type_interface_peek_parent (iface); + iface->add_child = sysprof_time_scrubber_add_child; +} diff --git a/src/libsysprof-ui/sysprof-recording-state-view.h b/src/sysprof/sysprof-time-scrubber.h similarity index 52% rename from src/libsysprof-ui/sysprof-recording-state-view.h rename to src/sysprof/sysprof-time-scrubber.h index b53481f4..6b2b6a33 100644 --- a/src/libsysprof-ui/sysprof-recording-state-view.h +++ b/src/sysprof/sysprof-time-scrubber.h @@ -1,6 +1,6 @@ -/* sysprof-recording-state-view.h +/* sysprof-time-scrubber.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,21 +21,20 @@ #pragma once #include + #include G_BEGIN_DECLS -#define SYSPROF_TYPE_RECORDING_STATE_VIEW (sysprof_recording_state_view_get_type()) +#define SYSPROF_TYPE_TIME_SCRUBBER (sysprof_time_scrubber_get_type()) -G_DECLARE_DERIVABLE_TYPE (SysprofRecordingStateView, sysprof_recording_state_view, SYSPROF, RECORDING_STATE_VIEW, GtkWidget) +G_DECLARE_FINAL_TYPE (SysprofTimeScrubber, sysprof_time_scrubber, SYSPROF, TIME_SCRUBBER, GtkWidget) -struct _SysprofRecordingStateViewClass -{ - GtkWidgetClass parent; -}; - -GtkWidget *sysprof_recording_state_view_new (void); -void sysprof_recording_state_view_set_profiler (SysprofRecordingStateView *self, - SysprofProfiler *profiler); +GtkWidget *sysprof_time_scrubber_new (void); +SysprofSession *sysprof_time_scrubber_get_session (SysprofTimeScrubber *self); +void sysprof_time_scrubber_set_session (SysprofTimeScrubber *self, + SysprofSession *session); +void sysprof_time_scrubber_add_chart (SysprofTimeScrubber *self, + GtkWidget *chart); G_END_DECLS diff --git a/src/sysprof/sysprof-time-scrubber.ui b/src/sysprof/sysprof-time-scrubber.ui new file mode 100644 index 00000000..0188f29a --- /dev/null +++ b/src/sysprof/sysprof-time-scrubber.ui @@ -0,0 +1,72 @@ + + + + diff --git a/src/libsysprof-ui/sysprof-visualizer-ticks.h b/src/sysprof/sysprof-time-series-item-private.h similarity index 67% rename from src/libsysprof-ui/sysprof-visualizer-ticks.h rename to src/sysprof/sysprof-time-series-item-private.h index cb4d6f5e..907b0ebd 100644 --- a/src/libsysprof-ui/sysprof-visualizer-ticks.h +++ b/src/sysprof/sysprof-time-series-item-private.h @@ -1,6 +1,6 @@ -/* sysprof-visualizer-ticks.h +/* sysprof-time-series-item-private.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,14 +22,12 @@ #include -#include "sysprof-visualizer.h" +#include "sysprof-time-series-item.h" G_BEGIN_DECLS -#define SYSPROF_TYPE_VISUALIZER_TICKS (sysprof_visualizer_ticks_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofVisualizerTicks, sysprof_visualizer_ticks, SYSPROF, VISUALIZER_TICKS, SysprofVisualizer) - -GtkWidget *sysprof_visualizer_ticks_new (void); +SysprofTimeSeriesItem *_sysprof_time_series_item_new (GObject *item, + GtkExpression *x_expression, + GtkExpression *y_expression); G_END_DECLS diff --git a/src/sysprof/sysprof-time-series-item.c b/src/sysprof/sysprof-time-series-item.c new file mode 100644 index 00000000..7c1bae36 --- /dev/null +++ b/src/sysprof/sysprof-time-series-item.c @@ -0,0 +1,192 @@ +/* sysprof-time-series-item.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-time-series-item-private.h" + +struct _SysprofTimeSeriesItem +{ + GObject parent_instance; + GtkExpression *begin_time_expression; + GtkExpression *end_time_expression; + GObject *item; +}; + +enum { + PROP_0, + PROP_DURATION, + PROP_ITEM, + PROP_BEGIN_TIME, + PROP_END_TIME, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofTimeSeriesItem, sysprof_time_series_item, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_time_series_item_finalize (GObject *object) +{ + SysprofTimeSeriesItem *self = (SysprofTimeSeriesItem *)object; + + g_clear_object (&self->item); + g_clear_pointer (&self->begin_time_expression, gtk_expression_unref); + g_clear_pointer (&self->end_time_expression, gtk_expression_unref); + + G_OBJECT_CLASS (sysprof_time_series_item_parent_class)->finalize (object); +} + +static void +sysprof_time_series_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeSeriesItem *self = SYSPROF_TIME_SERIES_ITEM (object); + + switch (prop_id) + { + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + case PROP_DURATION: + g_value_set_int64 (value, sysprof_time_series_item_get_duration (self)); + break; + + case PROP_BEGIN_TIME: + g_value_set_int64 (value, sysprof_time_series_item_get_begin_time (self)); + break; + + case PROP_END_TIME: + g_value_set_int64 (value, sysprof_time_series_item_get_end_time (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_series_item_class_init (SysprofTimeSeriesItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_time_series_item_finalize; + object_class->get_property = sysprof_time_series_item_get_property; + + properties [PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_BEGIN_TIME] = + g_param_spec_int64 ("begin-time", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_END_TIME] = + g_param_spec_int64 ("end-time", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_DURATION] = + g_param_spec_int64 ("duration", NULL, NULL, + G_MININT64, G_MAXINT64, 0, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_time_series_item_init (SysprofTimeSeriesItem *self) +{ +} + +SysprofTimeSeriesItem * +_sysprof_time_series_item_new (GObject *item, + GtkExpression *begin_time_expression, + GtkExpression *end_time_expression) +{ + SysprofTimeSeriesItem *self; + + self = g_object_new (SYSPROF_TYPE_TIME_SERIES_ITEM, NULL); + self->item = item; + self->begin_time_expression = begin_time_expression; + self->end_time_expression = end_time_expression; + + return self; +} + +/** + * sysprof_time_series_item_get_item: + * @self: a #SysprofTimeSeriesItem + * + * Gets the item used to generate X/Y values. + * + * Returns: (transfer none): a #GObject + */ +gpointer +sysprof_time_series_item_get_item (SysprofTimeSeriesItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES_ITEM (self), NULL); + + return self->item; +} + +gint64 +sysprof_time_series_item_get_begin_time (SysprofTimeSeriesItem *self) +{ + GValue value = G_VALUE_INIT; + gint64 ret; + + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES_ITEM (self), 0); + + g_value_init (&value, G_TYPE_INT64); + gtk_expression_evaluate (self->begin_time_expression, self->item, &value); + ret = g_value_get_int64 (&value); + g_value_unset (&value); + + return ret; +} + +gint64 +sysprof_time_series_item_get_duration (SysprofTimeSeriesItem *self) +{ + return sysprof_time_series_item_get_end_time (self) - sysprof_time_series_item_get_begin_time (self); +} + +gint64 +sysprof_time_series_item_get_end_time (SysprofTimeSeriesItem *self) +{ + GValue value = G_VALUE_INIT; + gint64 ret; + + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES_ITEM (self), 0); + + g_value_init (&value, G_TYPE_INT64); + gtk_expression_evaluate (self->end_time_expression, self->item, &value); + ret = g_value_get_int64 (&value); + g_value_unset (&value); + + return ret; +} diff --git a/src/sysprof/sysprof-time-series-item.h b/src/sysprof/sysprof-time-series-item.h new file mode 100644 index 00000000..fba4d6a7 --- /dev/null +++ b/src/sysprof/sysprof-time-series-item.h @@ -0,0 +1,38 @@ +/* sysprof-time-series-item.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_SERIES_ITEM (sysprof_time_series_item_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTimeSeriesItem, sysprof_time_series_item, SYSPROF, TIME_SERIES_ITEM, GObject) + +gint64 sysprof_time_series_item_get_begin_time (SysprofTimeSeriesItem *self); +gint64 sysprof_time_series_item_get_duration (SysprofTimeSeriesItem *self); +gint64 sysprof_time_series_item_get_end_time (SysprofTimeSeriesItem *self); +gpointer sysprof_time_series_item_get_item (SysprofTimeSeriesItem *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-time-series.c b/src/sysprof/sysprof-time-series.c new file mode 100644 index 00000000..1b8b1c9b --- /dev/null +++ b/src/sysprof/sysprof-time-series.c @@ -0,0 +1,336 @@ +/* sysprof-time-series.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-series-private.h" +#include "sysprof-time-series.h" +#include "sysprof-time-series-item-private.h" + +struct _SysprofTimeSeries +{ + SysprofSeries parent_instance; + GtkExpression *begin_time_expression; + GtkExpression *end_time_expression; + GtkExpression *label_expression; +}; + +struct _SysprofTimeSeriesClass +{ + SysprofSeriesClass parent_instance; +}; + +enum { + PROP_0, + PROP_BEGIN_TIME_EXPRESSION, + PROP_END_TIME_EXPRESSION, + PROP_LABEL_EXPRESSION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofTimeSeries, sysprof_time_series, SYSPROF_TYPE_SERIES) + +static GParamSpec *properties [N_PROPS]; + +static gpointer +sysprof_time_series_get_series_item (SysprofSeries *series, + guint position, + gpointer item) +{ + SysprofTimeSeries *self = SYSPROF_TIME_SERIES (series); + + return _sysprof_time_series_item_new (item, + gtk_expression_ref (self->begin_time_expression), + gtk_expression_ref (self->end_time_expression)); +} + +static void +sysprof_time_series_finalize (GObject *object) +{ + SysprofTimeSeries *self = (SysprofTimeSeries *)object; + + g_clear_pointer (&self->end_time_expression, gtk_expression_unref); + g_clear_pointer (&self->label_expression, gtk_expression_unref); + g_clear_pointer (&self->begin_time_expression, gtk_expression_unref); + + G_OBJECT_CLASS (sysprof_time_series_parent_class)->finalize (object); +} + +static void +sysprof_time_series_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeSeries *self = SYSPROF_TIME_SERIES (object); + + switch (prop_id) + { + case PROP_BEGIN_TIME_EXPRESSION: + gtk_value_set_expression (value, self->begin_time_expression); + break; + + case PROP_END_TIME_EXPRESSION: + gtk_value_set_expression (value, self->end_time_expression); + break; + + case PROP_LABEL_EXPRESSION: + gtk_value_set_expression (value, self->label_expression); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_series_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTimeSeries *self = SYSPROF_TIME_SERIES (object); + + switch (prop_id) + { + case PROP_BEGIN_TIME_EXPRESSION: + sysprof_time_series_set_begin_time_expression (self, gtk_value_get_expression (value)); + break; + + case PROP_END_TIME_EXPRESSION: + sysprof_time_series_set_end_time_expression (self, gtk_value_get_expression (value)); + break; + + case PROP_LABEL_EXPRESSION: + sysprof_time_series_set_label_expression (self, gtk_value_get_expression (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_series_class_init (SysprofTimeSeriesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSeriesClass *series_class = SYSPROF_SERIES_CLASS (klass); + + object_class->finalize = sysprof_time_series_finalize; + object_class->get_property = sysprof_time_series_get_property; + object_class->set_property = sysprof_time_series_set_property; + + series_class->series_item_type = SYSPROF_TYPE_TIME_SERIES_ITEM; + series_class->get_series_item = sysprof_time_series_get_series_item; + + properties [PROP_BEGIN_TIME_EXPRESSION] = + gtk_param_spec_expression ("begin-time-expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_END_TIME_EXPRESSION] = + gtk_param_spec_expression ("end-time-expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_LABEL_EXPRESSION] = + gtk_param_spec_expression ("label-expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_time_series_init (SysprofTimeSeries *self) +{ +} + +/** + * sysprof_time_series_new: + * @title: (nullable): a title for the series + * @model: (transfer full) (nullable): a #GListModel for the series + * @begin_time_expression: (transfer full) (nullable): a #GtkExpression for + * extracting the begin time value from @model items. + * @end_time_expression: (transfer full) (nullable): a #GtkExpression for + * extracting the end time value from @model items. + * @label_expression: (transfer full) (nullable): a #GtkExpression for + * extracting the label from @model items. + * + * A #SysprofSeries which contains Time,Duration pairs. + * + * Returns: (transfer full): a #SysprofSeries + */ +SysprofSeries * +sysprof_time_series_new (const char *title, + GListModel *model, + GtkExpression *begin_time_expression, + GtkExpression *end_time_expression, + GtkExpression *label_expression) +{ + SysprofTimeSeries *xy; + + xy = g_object_new (SYSPROF_TYPE_TIME_SERIES, + "title", title, + "model", model, + "begin-time-expression", begin_time_expression, + "end-time-expression", end_time_expression, + "label-expression", label_expression, + NULL); + + g_clear_pointer (&begin_time_expression, gtk_expression_unref); + g_clear_pointer (&end_time_expression, gtk_expression_unref); + g_clear_pointer (&label_expression, gtk_expression_unref); + g_clear_object (&model); + + return SYSPROF_SERIES (xy); +} + +/** + * sysprof_time_series_get_begin_time_expression: + * @self: a #SysprofTimeSeries + * + * Gets the #SysprofTimeSeries:x-expression property. + * + * This is used to extract the X coordinate from items in the #GListModel. + * + * Returns: (transfer none) (nullable): a #GtkExpression or %NULL + */ +GtkExpression * +sysprof_time_series_get_begin_time_expression (SysprofTimeSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES (self), NULL); + + return self->begin_time_expression; +} + +void +sysprof_time_series_set_begin_time_expression (SysprofTimeSeries *self, + GtkExpression *begin_time_expression) +{ + g_return_if_fail (SYSPROF_IS_TIME_SERIES (self)); + + if (self->begin_time_expression == begin_time_expression) + return; + + if (begin_time_expression) + gtk_expression_ref (begin_time_expression); + + g_clear_pointer (&self->begin_time_expression, gtk_expression_unref); + + self->begin_time_expression = begin_time_expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BEGIN_TIME_EXPRESSION]); +} + +/** + * sysprof_time_series_get_end_time_expression: + * @self: a #SysprofTimeSeries + * + * Gets the #SysprofTimeSeries:y-expression property. + * + * This is used to extract the Y coordinate from items in the #GListModel. + * + * Returns: (transfer none) (nullable): a #GtkExpression or %NULL + */ +GtkExpression * +sysprof_time_series_get_end_time_expression (SysprofTimeSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES (self), NULL); + + return self->end_time_expression; +} + +void +sysprof_time_series_set_end_time_expression (SysprofTimeSeries *self, + GtkExpression *end_time_expression) +{ + g_return_if_fail (SYSPROF_IS_TIME_SERIES (self)); + + if (self->end_time_expression == end_time_expression) + return; + + if (end_time_expression) + gtk_expression_ref (end_time_expression); + + g_clear_pointer (&self->end_time_expression, gtk_expression_unref); + + self->end_time_expression = end_time_expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_END_TIME_EXPRESSION]); +} + +/** + * sysprof_time_series_get_label_expression: + * @self: a #SysprofTimeSeries + * + * Gets the #SysprofTimeSeries:y-expression property. + * + * This is used to extract the Y coordinate from items in the #GListModel. + * + * Returns: (transfer none) (nullable): a #GtkExpression or %NULL + */ +GtkExpression * +sysprof_time_series_get_label_expression (SysprofTimeSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES (self), NULL); + + return self->label_expression; +} + +void +sysprof_time_series_set_label_expression (SysprofTimeSeries *self, + GtkExpression *label_expression) +{ + g_return_if_fail (SYSPROF_IS_TIME_SERIES (self)); + + if (self->label_expression == label_expression) + return; + + if (label_expression) + gtk_expression_ref (label_expression); + + g_clear_pointer (&self->label_expression, gtk_expression_unref); + + self->label_expression = label_expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LABEL_EXPRESSION]); +} + +char * +sysprof_time_series_dup_label (SysprofTimeSeries *self, + guint position) +{ + g_autoptr(GObject) item = NULL; + g_auto(GValue) value = G_VALUE_INIT; + GListModel *model; + + g_return_val_if_fail (SYSPROF_IS_TIME_SERIES (self), NULL); + + if (self->label_expression == NULL) + return NULL; + + if (!(model = sysprof_series_get_model (SYSPROF_SERIES (self)))) + return NULL; + + if (!(item = g_list_model_get_item (model, position))) + return NULL; + + g_value_init (&value, G_TYPE_STRING); + gtk_expression_evaluate (self->label_expression, item, &value); + return g_value_dup_string (&value); +} diff --git a/src/sysprof/sysprof-time-series.h b/src/sysprof/sysprof-time-series.h new file mode 100644 index 00000000..51cbdb85 --- /dev/null +++ b/src/sysprof/sysprof-time-series.h @@ -0,0 +1,57 @@ +/* sysprof-time-series.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_SERIES (sysprof_time_series_get_type()) +#define SYSPROF_IS_TIME_SERIES(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_TIME_SERIES)) +#define SYSPROF_TIME_SERIES(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_TIME_SERIES, SysprofTimeSeries)) +#define SYSPROF_TIME_SERIES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_TIME_SERIES, SysprofTimeSeriesClass)) + +typedef struct _SysprofTimeSeries SysprofTimeSeries; +typedef struct _SysprofTimeSeriesClass SysprofTimeSeriesClass; + +GType sysprof_time_series_get_type (void) G_GNUC_CONST; +SysprofSeries *sysprof_time_series_new (const char *title, + GListModel *model, + GtkExpression *begin_time_expression, + GtkExpression *end_time_expression, + GtkExpression *label_expression); +GtkExpression *sysprof_time_series_get_begin_time_expression (SysprofTimeSeries *self); +void sysprof_time_series_set_begin_time_expression (SysprofTimeSeries *self, + GtkExpression *time_expression); +GtkExpression *sysprof_time_series_get_end_time_expression (SysprofTimeSeries *self); +void sysprof_time_series_set_end_time_expression (SysprofTimeSeries *self, + GtkExpression *end_time_expression); +GtkExpression *sysprof_time_series_get_label_expression (SysprofTimeSeries *self); +void sysprof_time_series_set_label_expression (SysprofTimeSeries *self, + GtkExpression *label_expression); +char *sysprof_time_series_dup_label (SysprofTimeSeries *self, + guint position); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofTimeSeries, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-time-span-layer.c b/src/sysprof/sysprof-time-span-layer.c new file mode 100644 index 00000000..2427d8b3 --- /dev/null +++ b/src/sysprof/sysprof-time-span-layer.c @@ -0,0 +1,581 @@ +/* + * sysprof-time-span-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-chart-layer-private.h" +#include "sysprof-normalized-series.h" +#include "sysprof-time-series-item.h" +#include "sysprof-time-span-layer.h" + +struct _SysprofTimeSpanLayer +{ + SysprofChartLayer parent_instance; + + SysprofAxis *axis; + SysprofTimeSeries *series; + + SysprofNormalizedSeries *normal_x; + SysprofNormalizedSeries *normal_x2; + + GBindingGroup *series_bindings; + + GdkRGBA color; + GdkRGBA event_color; + + guint color_set : 1; + guint event_color_set : 1; +}; + +G_DEFINE_FINAL_TYPE (SysprofTimeSpanLayer, sysprof_time_span_layer, SYSPROF_TYPE_CHART_LAYER) + +enum { + PROP_0, + PROP_AXIS, + PROP_COLOR, + PROP_EVENT_COLOR, + PROP_NORMALIZED_X, + PROP_NORMALIZED_X2, + PROP_SERIES, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +SysprofChartLayer * +sysprof_time_span_layer_new (void) +{ + return g_object_new (SYSPROF_TYPE_TIME_SPAN_LAYER, NULL); +} + +static inline void +premix_colors (GdkRGBA *dest, + const GdkRGBA *fg, + const GdkRGBA *bg, + gboolean bg_set, + double alpha) +{ + g_assert (dest != NULL); + g_assert (fg != NULL); + g_assert (bg != NULL || bg_set == FALSE); + g_assert (alpha >= 0.0 && alpha <= 1.0); + + if (bg_set) + { + dest->red = ((1 - alpha) * bg->red) + (alpha * fg->red); + dest->green = ((1 - alpha) * bg->green) + (alpha * fg->green); + dest->blue = ((1 - alpha) * bg->blue) + (alpha * fg->blue); + dest->alpha = 1.0; + } + else + { + *dest = *fg; + dest->alpha = alpha; + } +} + +static void +sysprof_time_span_layer_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + SysprofTimeSpanLayer *self = (SysprofTimeSpanLayer *)widget; + const double *x_values; + const double *x2_values; + const GdkRGBA *color; + const GdkRGBA *event_color; + GdkRGBA mixed; + graphene_rect_t box_rect; + guint n_x_values = 0; + guint n_x2_values = 0; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_TIME_SPAN_LAYER (self)); + g_assert (GTK_IS_SNAPSHOT (snapshot)); + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + if (self->color_set) + color = &self->color; + else + color = _sysprof_chart_layer_get_accent_bg_color (); + + if (self->event_color_set) + event_color = &self->event_color; + else + { + premix_colors (&mixed, + _sysprof_chart_layer_get_accent_bg_color (), + _sysprof_chart_layer_get_accent_fg_color (), + TRUE, .5); + event_color = &mixed; + } + + if (width == 0 || height == 0) + return; + + if (!(x_values = sysprof_normalized_series_get_values (self->normal_x, &n_x_values)) || + !(x2_values = sysprof_normalized_series_get_values (self->normal_x2, &n_x2_values)) || + !(n_values = MIN (n_x_values, n_x2_values))) + return; + + if (color->alpha > 0) + { + const GdkRGBA *label_color = _sysprof_chart_layer_get_accent_fg_color (); + PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), NULL); + int layout_y; + int layout_h; + + pango_layout_set_single_paragraph_mode (layout, TRUE); + pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); + pango_layout_set_text (layout, "XM0", -1); + pango_layout_get_pixel_size (layout, NULL, &layout_h); + + layout_y = (height - layout_h) / 2; + + /* First pass, draw our rectangles for duration which we + * always want in the background compared to "points" which + * are marks w/o a duration. + */ + for (guint i = 0; i < n_values; i++) + { + double begin = x_values[i]; + double end = x2_values[i]; + + if (end < .0) + continue; + + if (begin > 1.) + break; + + if (begin != end) + { + graphene_rect_t rect; + + rect = GRAPHENE_RECT_INIT (floor (begin * width), + 0, + ceil ((end - begin) * width), + height); + + /* Ignore empty draws */ + if (rect.size.width == 0) + continue; + + if (rect.origin.x < 0) + { + rect.size.width -= ABS (rect.origin.x); + rect.origin.x = 0; + } + + if (rect.origin.x + rect.size.width > width) + rect.size.width = width - rect.origin.x; + + gtk_snapshot_append_color (snapshot, color, &rect); + + if (rect.size.width > 20) + { + g_autofree char *label = sysprof_time_series_dup_label (self->series, i); + + if (label != NULL) + { + pango_layout_set_text (layout, label, -1); + pango_layout_set_width (layout, (rect.size.width - 6) * PANGO_SCALE); + + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (rect.origin.x + 3, layout_y)); + gtk_snapshot_append_layout (snapshot, layout, label_color); + gtk_snapshot_restore (snapshot); + } + } + } + } + + g_object_unref (layout); + } + + if (event_color->alpha > 0) + { + double last_x = -1; + + box_rect = GRAPHENE_RECT_INIT (-ceil (height / 6.), + -ceil (height / 6.), + ceil (height / 3.), + ceil (height / 3.)); + + for (guint i = 0; i < n_values; i++) + { + double begin = x_values[i]; + double end = x2_values[i]; + + if (begin != end) + continue; + + if (last_x != begin) + { + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, + &GRAPHENE_POINT_INIT (begin * width, height / 2)); + gtk_snapshot_rotate (snapshot, 45.f); + gtk_snapshot_append_color (snapshot, event_color, &box_rect); + gtk_snapshot_restore (snapshot); + + last_x = begin; + } + } + } +} + +static gpointer +sysprof_time_span_layer_lookup_item (SysprofChartLayer *layer, + double x, + double y) +{ + SysprofTimeSpanLayer *self = (SysprofTimeSpanLayer *)layer; + const double *x_values; + const double *x2_values; + GListModel *model; + guint n_x_values = 0; + guint n_x2_values = 0; + guint n_values; + int width; + int height; + + g_assert (SYSPROF_IS_TIME_SPAN_LAYER (self)); + + width = gtk_widget_get_width (GTK_WIDGET (self)); + height = gtk_widget_get_height (GTK_WIDGET (self)); + + if (width == 0 || height == 0 || self->series == NULL) + return NULL; + + if (!(model = sysprof_series_get_model (SYSPROF_SERIES (self->series)))) + return NULL; + + if (!(x_values = sysprof_normalized_series_get_values (self->normal_x, &n_x_values)) || + !(x2_values = sysprof_normalized_series_get_values (self->normal_x2, &n_x2_values)) || + !(n_values = MIN (n_x_values, n_x2_values))) + return NULL; + + /* First match our non-duration marks (diamonds) */ + for (guint i = 0; i < n_values; i++) + { + int begin = x_values[i] * width - 3; + int end = x2_values[i] * width + 3; + + if (x_values[i] != x2_values[i]) + continue; + + if (x >= begin && x < end) + return g_list_model_get_item (model, i); + } + + /* Then match regular duration events */ + for (guint i = 0; i < n_values; i++) + { + double begin = x_values[i] * width; + double end = x2_values[i] * width; + + if (x_values[i] == x2_values[i]) + continue; + + if (x >= begin && x < end) + return g_list_model_get_item (model, i); + } + + return NULL; +} + +static void +sysprof_time_span_layer_finalize (GObject *object) +{ + SysprofTimeSpanLayer *self = (SysprofTimeSpanLayer *)object; + + g_clear_object (&self->series_bindings); + g_clear_object (&self->axis); + g_clear_object (&self->series); + g_clear_object (&self->normal_x); + g_clear_object (&self->normal_x2); + + G_OBJECT_CLASS (sysprof_time_span_layer_parent_class)->finalize (object); +} + +static void +sysprof_time_span_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTimeSpanLayer *self = SYSPROF_TIME_SPAN_LAYER (object); + + switch (prop_id) + { + case PROP_AXIS: + g_value_set_object (value, sysprof_time_span_layer_get_axis (self)); + break; + + case PROP_COLOR: + g_value_set_boxed (value, sysprof_time_span_layer_get_color (self)); + break; + + case PROP_EVENT_COLOR: + g_value_set_boxed (value, sysprof_time_span_layer_get_event_color (self)); + break; + + case PROP_NORMALIZED_X: + g_value_set_object (value, self->normal_x); + break; + + case PROP_NORMALIZED_X2: + g_value_set_object (value, self->normal_x2); + break; + + case PROP_SERIES: + g_value_set_object (value, sysprof_time_span_layer_get_series (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_span_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTimeSpanLayer *self = SYSPROF_TIME_SPAN_LAYER (object); + + switch (prop_id) + { + case PROP_AXIS: + sysprof_time_span_layer_set_axis (self, g_value_get_object (value)); + break; + + case PROP_COLOR: + sysprof_time_span_layer_set_color (self, g_value_get_boxed (value)); + break; + + case PROP_EVENT_COLOR: + sysprof_time_span_layer_set_event_color (self, g_value_get_boxed (value)); + break; + + case PROP_SERIES: + sysprof_time_span_layer_set_series (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_time_span_layer_class_init (SysprofTimeSpanLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + SysprofChartLayerClass *chart_layer_class = SYSPROF_CHART_LAYER_CLASS (klass); + + object_class->finalize = sysprof_time_span_layer_finalize; + object_class->get_property = sysprof_time_span_layer_get_property; + object_class->set_property = sysprof_time_span_layer_set_property; + + widget_class->snapshot = sysprof_time_span_layer_snapshot; + + chart_layer_class->lookup_item = sysprof_time_span_layer_lookup_item; + + properties[PROP_AXIS] = + g_param_spec_object ("axis", NULL, NULL, + SYSPROF_TYPE_AXIS, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_COLOR] = + g_param_spec_boxed ("color", NULL, NULL, + GDK_TYPE_RGBA, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_EVENT_COLOR] = + g_param_spec_boxed ("event-color", NULL, NULL, + GDK_TYPE_RGBA, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_NORMALIZED_X] = + g_param_spec_object ("normalized-x", NULL, NULL, + SYSPROF_TYPE_NORMALIZED_SERIES, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_NORMALIZED_X2] = + g_param_spec_object ("normalized-x2", NULL, NULL, + SYSPROF_TYPE_NORMALIZED_SERIES, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_SERIES] = + g_param_spec_object ("series", NULL, NULL, + SYSPROF_TYPE_TIME_SERIES, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_time_span_layer_init (SysprofTimeSpanLayer *self) +{ + self->normal_x = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES, NULL); + self->normal_x2 = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES, NULL); + + g_signal_connect_object (self->normal_x, + "items-changed", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->normal_x2, + "items-changed", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); + + self->series_bindings = g_binding_group_new (); + g_binding_group_bind (self->series_bindings, "begin-time-expression", + self->normal_x, "expression", + G_BINDING_SYNC_CREATE); + g_binding_group_bind (self->series_bindings, "end-time-expression", + self->normal_x2, "expression", + G_BINDING_SYNC_CREATE); +} + +const GdkRGBA * +sysprof_time_span_layer_get_color (SysprofTimeSpanLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self), NULL); + + return &self->color; +} + +void +sysprof_time_span_layer_set_color (SysprofTimeSpanLayer *self, + const GdkRGBA *color) +{ + static const GdkRGBA black = {0,0,0,1}; + + g_return_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self)); + + if (color == NULL) + color = &black; + + if (!gdk_rgba_equal (&self->color, color)) + { + self->color = *color; + self->color_set = color != &black; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLOR]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +const GdkRGBA * +sysprof_time_span_layer_get_event_color (SysprofTimeSpanLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self), NULL); + + return &self->event_color; +} + +void +sysprof_time_span_layer_set_event_color (SysprofTimeSpanLayer *self, + const GdkRGBA *event_color) +{ + static const GdkRGBA black = {0,0,0,1}; + + g_return_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self)); + + if (event_color == NULL) + event_color = &black; + + if (!gdk_rgba_equal (&self->event_color, event_color)) + { + self->event_color = *event_color; + self->color_set = event_color != &black; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EVENT_COLOR]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +/** + * sysprof_time_span_layer_get_series: + * @self: a #SysprofTimeSpanLayer + * + * Returns: (transfer none) (nullable): a #SysprofTimeSeries or %NULL + */ +SysprofTimeSeries * +sysprof_time_span_layer_get_series (SysprofTimeSpanLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self), NULL); + + return self->series; +} + +void +sysprof_time_span_layer_set_series (SysprofTimeSpanLayer *self, + SysprofTimeSeries *series) +{ + g_return_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self)); + + if (!g_set_object (&self->series, series)) + return; + + g_binding_group_set_source (self->series_bindings, series); + + sysprof_normalized_series_set_series (self->normal_x, SYSPROF_SERIES (series)); + sysprof_normalized_series_set_series (self->normal_x2, SYSPROF_SERIES (series)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SERIES]); +} + +/** + * sysprof_time_span_layer_get_axis: + * @self: a #SysprofTimeSpanLayer + * + * Returns: (transfer none) (nullable): a #SysprofAxis or %NULL + */ +SysprofAxis * +sysprof_time_span_layer_get_axis (SysprofTimeSpanLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self), NULL); + + return self->axis; +} + +void +sysprof_time_span_layer_set_axis (SysprofTimeSpanLayer *self, + SysprofAxis *axis) +{ + g_return_if_fail (SYSPROF_IS_TIME_SPAN_LAYER (self)); + g_return_if_fail (!axis || SYSPROF_IS_AXIS (axis)); + + if (!g_set_object (&self->axis, axis)) + return; + + sysprof_normalized_series_set_axis (self->normal_x, axis); + sysprof_normalized_series_set_axis (self->normal_x2, axis); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AXIS]); +} diff --git a/src/sysprof/sysprof-time-span-layer.h b/src/sysprof/sysprof-time-span-layer.h new file mode 100644 index 00000000..9caf6a8d --- /dev/null +++ b/src/sysprof/sysprof-time-span-layer.h @@ -0,0 +1,49 @@ +/* + * sysprof-time-span-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-axis.h" +#include "sysprof-chart-layer.h" +#include "sysprof-time-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TIME_SPAN_LAYER (sysprof_time_span_layer_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTimeSpanLayer, sysprof_time_span_layer, SYSPROF, TIME_SPAN_LAYER, SysprofChartLayer) + +SysprofChartLayer *sysprof_time_span_layer_new (void); +const GdkRGBA *sysprof_time_span_layer_get_color (SysprofTimeSpanLayer *self); +void sysprof_time_span_layer_set_color (SysprofTimeSpanLayer *self, + const GdkRGBA *color); +const GdkRGBA *sysprof_time_span_layer_get_event_color (SysprofTimeSpanLayer *self); +void sysprof_time_span_layer_set_event_color (SysprofTimeSpanLayer *self, + const GdkRGBA *event_color); +SysprofTimeSeries *sysprof_time_span_layer_get_series (SysprofTimeSpanLayer *self); +void sysprof_time_span_layer_set_series (SysprofTimeSpanLayer *self, + SysprofTimeSeries *series); +SysprofAxis *sysprof_time_span_layer_get_axis (SysprofTimeSpanLayer *self); +void sysprof_time_span_layer_set_axis (SysprofTimeSpanLayer *self, + SysprofAxis *axis); + +G_END_DECLS + diff --git a/src/sysprof/sysprof-traceables-utility.c b/src/sysprof/sysprof-traceables-utility.c new file mode 100644 index 00000000..d0f30182 --- /dev/null +++ b/src/sysprof/sysprof-traceables-utility.c @@ -0,0 +1,185 @@ +/* sysprof-traceables-utility.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-session.h" +#include "sysprof-traceables-utility.h" + +struct _SysprofTraceablesUtility +{ + GtkWidget parent_instance; + SysprofSession *session; + GListModel *traceables; +}; + +enum { + PROP_0, + PROP_SESSION, + PROP_TRACEABLES, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofTraceablesUtility, sysprof_traceables_utility, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS]; + +static char * +format_time_offset (gpointer cell) +{ + g_autoptr(SysprofDocumentFrame) frame = NULL; + int hours; + int minutes; + double time; + + g_object_get (cell, "item", &frame, NULL); + g_assert (!frame || SYSPROF_IS_DOCUMENT_FRAME (frame)); + + if (!frame) + return NULL; + + time = sysprof_document_frame_get_time_offset (frame) / (double)SYSPROF_NSEC_PER_SEC; + + hours = time / (60 * 60); + time -= hours * (60 * 60); + + minutes = time / 60; + time -= minutes * 60; + + if (hours == 0 && minutes == 0) + return g_strdup_printf ("%.4lf", time); + + if (hours == 0) + return g_strdup_printf ("%02d:%02.4lf", minutes, time); + + return g_strdup_printf ("%02d:%02d:%02.4lf", hours, minutes, time); +} + +static GListModel * +symbolize_traceable (SysprofTraceablesUtility *self, + SysprofDocumentTraceable *traceable) +{ + SysprofDocument *document; + + g_assert (SYSPROF_IS_TRACEABLES_UTILITY (self)); + g_assert (!traceable || SYSPROF_IS_DOCUMENT_TRACEABLE (traceable)); + + if (traceable == NULL || self->session == NULL || + !(document = sysprof_session_get_document (self->session))) + return NULL; + + return sysprof_document_list_symbols_in_traceable (document, traceable); +} + +static void +sysprof_traceables_utility_finalize (GObject *object) +{ + SysprofTraceablesUtility *self = (SysprofTraceablesUtility *)object; + GtkWidget *child; + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_TRACEABLES_UTILITY); + + while ((child = gtk_widget_get_first_child (GTK_WIDGET (object)))) + gtk_widget_unparent (child); + + g_clear_object (&self->session); + + G_OBJECT_CLASS (sysprof_traceables_utility_parent_class)->finalize (object); +} + +static void +sysprof_traceables_utility_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTraceablesUtility *self = SYSPROF_TRACEABLES_UTILITY (object); + + switch (prop_id) + { + case PROP_SESSION: + g_value_set_object (value, self->session); + break; + + case PROP_TRACEABLES: + g_value_set_object (value, self->traceables); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_traceables_utility_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTraceablesUtility *self = SYSPROF_TRACEABLES_UTILITY (object); + + switch (prop_id) + { + case PROP_SESSION: + g_set_object (&self->session, g_value_get_object (value)); + break; + + case PROP_TRACEABLES: + g_set_object (&self->traceables, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_traceables_utility_class_init (SysprofTraceablesUtilityClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = sysprof_traceables_utility_finalize; + object_class->get_property = sysprof_traceables_utility_get_property; + object_class->set_property = sysprof_traceables_utility_set_property; + + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + properties[PROP_TRACEABLES] = + g_param_spec_object ("traceables", NULL, NULL, + G_TYPE_LIST_MODEL, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-traceables-utility.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_bind_template_callback (widget_class, format_time_offset); + gtk_widget_class_bind_template_callback (widget_class, symbolize_traceable); +} + +static void +sysprof_traceables_utility_init (SysprofTraceablesUtility *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/src/sysprof/sysprof-traceables-utility.h b/src/sysprof/sysprof-traceables-utility.h new file mode 100644 index 00000000..f76063b7 --- /dev/null +++ b/src/sysprof/sysprof-traceables-utility.h @@ -0,0 +1,31 @@ +/* sysprof-traceables-utility.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TRACEABLES_UTILITY (sysprof_traceables_utility_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTraceablesUtility, sysprof_traceables_utility, SYSPROF, TRACEABLES_UTILITY, GtkWidget) + +G_END_DECLS diff --git a/src/sysprof/sysprof-traceables-utility.ui b/src/sysprof/sysprof-traceables-utility.ui new file mode 100644 index 00000000..efe7d4ff --- /dev/null +++ b/src/sysprof/sysprof-traceables-utility.ui @@ -0,0 +1,200 @@ + + + + diff --git a/src/sysprof/sysprof-tree-expander.c b/src/sysprof/sysprof-tree-expander.c new file mode 100644 index 00000000..0da9b6e7 --- /dev/null +++ b/src/sysprof/sysprof-tree-expander.c @@ -0,0 +1,875 @@ +/* sysprof-tree-expander.c + * + * Copyright 2022-2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-tree-expander.h" + +struct _SysprofTreeExpander +{ + GtkWidget parent_instance; + + GtkWidget *expander; + GtkWidget *image; + GtkWidget *child; + GtkWidget *suffix; + + GMenuModel *menu_model; + + GtkTreeListRow *list_row; + + GIcon *icon; + GIcon *expanded_icon; + + GtkPopover *popover; + + gulong list_row_notify_expanded; +}; + +enum { + PROP_0, + PROP_CHILD, + PROP_EXPANDED, + PROP_EXPANDED_ICON, + PROP_EXPANDED_ICON_NAME, + PROP_ICON, + PROP_ICON_NAME, + PROP_ITEM, + PROP_LIST_ROW, + PROP_MENU_MODEL, + PROP_SUFFIX, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofTreeExpander, sysprof_tree_expander, GTK_TYPE_WIDGET) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_tree_expander_update_depth (SysprofTreeExpander *self) +{ + static GType builtin_icon_type = G_TYPE_INVALID; + GtkWidget *child; + guint depth; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + + if (self->list_row != NULL) + depth = gtk_tree_list_row_get_depth (self->list_row); + else + depth = 0; + + if G_UNLIKELY (builtin_icon_type == G_TYPE_INVALID) + builtin_icon_type = g_type_from_name ("GtkBuiltinIcon"); + + g_assert (builtin_icon_type != G_TYPE_INVALID); + + child = gtk_widget_get_prev_sibling (self->expander); + + while (child) + { + GtkWidget *prev = gtk_widget_get_prev_sibling (child); + g_assert (G_TYPE_CHECK_INSTANCE_TYPE (child, builtin_icon_type)); + gtk_widget_unparent (child); + child = prev; + } + + for (guint i = 0; i < depth; i++) + { + child = g_object_new (builtin_icon_type, + "css-name", "indent", + "accessible-role", GTK_ACCESSIBLE_ROLE_PRESENTATION, + NULL); + gtk_widget_insert_after (child, GTK_WIDGET (self), NULL); + } + + /* The level property is >= 1 */ + gtk_accessible_update_property (GTK_ACCESSIBLE (self), + GTK_ACCESSIBLE_PROPERTY_LEVEL, depth + 1, + -1); +} + +static void +sysprof_tree_expander_update_icon (SysprofTreeExpander *self) +{ + GIcon *icon = NULL; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + g_assert (gtk_widget_get_parent (self->image) == GTK_WIDGET (self)); + + if (self->list_row != NULL) + { + if (gtk_tree_list_row_get_expanded (self->list_row)) + { + icon = self->expanded_icon ? self->expanded_icon : self->icon; + gtk_widget_set_state_flags (self->expander, GTK_STATE_FLAG_CHECKED, FALSE); + } + else + { + icon = self->icon; + gtk_widget_unset_state_flags (self->expander, GTK_STATE_FLAG_CHECKED); + } + + if (gtk_tree_list_row_is_expandable (self->list_row)) + gtk_widget_add_css_class (GTK_WIDGET (self->expander), "expandable"); + else + gtk_widget_remove_css_class (GTK_WIDGET (self->expander), "expandable"); + } + + gtk_image_set_from_gicon (GTK_IMAGE (self->image), icon); + + gtk_widget_set_visible (self->image, icon != NULL); +} + +static void +sysprof_tree_expander_notify_expanded_cb (SysprofTreeExpander *self, + GParamSpec *pspec, + GtkTreeListRow *list_row) +{ + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + g_assert (GTK_IS_TREE_LIST_ROW (list_row)); + + sysprof_tree_expander_update_icon (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EXPANDED]); +} + +static void +sysprof_tree_expander_click_pressed_cb (SysprofTreeExpander *self, + int n_press, + double x, + double y, + GtkGestureClick *click) +{ + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + g_assert (GTK_IS_GESTURE_CLICK (click)); + + if (n_press != 1 || self->list_row == NULL) + return; + + gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE, FALSE); +} + +static gboolean +expander_contains_point (SysprofTreeExpander *self, + double x, + double y) +{ + gtk_widget_translate_coordinates (GTK_WIDGET (self), GTK_WIDGET (self->expander), + x, y, &x, &y); + + if (x < 0 || y < 0 || x > gtk_widget_get_width (self->expander) || y > gtk_widget_get_height (self->expander)) + return FALSE; + + return TRUE; +} + +static void +sysprof_tree_expander_click_released_cb (SysprofTreeExpander *self, + int n_press, + double x, + double y, + GtkGestureClick *click) +{ + static GType column_view_row_gtype; + static GType list_item_widget_gtype; + GtkWidget *row; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + g_assert (GTK_IS_GESTURE_CLICK (click)); + + if G_UNLIKELY (!column_view_row_gtype) + column_view_row_gtype = g_type_from_name ("GtkColumnViewRowWidget"); + + if G_UNLIKELY (!list_item_widget_gtype) + list_item_widget_gtype = g_type_from_name ("GtkListItemWidget"); + + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); + + if (!(row = gtk_widget_get_ancestor (GTK_WIDGET (self), column_view_row_gtype)) && + !(row = gtk_widget_get_ancestor (GTK_WIDGET (self), list_item_widget_gtype))) + return; + + gtk_widget_activate_action (row, "listitem.select", "(bb)", FALSE, FALSE); + + if (n_press == 1 && + gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (click)) == 3 && + self->menu_model != NULL) + { + GtkPopover *popover; + GdkRectangle rect; + + rect.x = x; + rect.width = 1; + rect.height = gtk_widget_get_height (GTK_WIDGET (self)); + rect.y = 0; + + popover = g_object_new (GTK_TYPE_POPOVER_MENU, + "menu-model", self->menu_model, + "position", GTK_POS_RIGHT, + "pointing-to", &rect, + NULL); + sysprof_tree_expander_show_popover (self, popover); + } + else if (n_press == 2 || + (n_press == 1 && expander_contains_point (self, x, y))) + { + gtk_widget_activate_action (GTK_WIDGET (self), "listitem.toggle-expand", NULL); + } + + gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static void +sysprof_tree_expander_click_cancel_cb (SysprofTreeExpander *self, + GdkEventSequence *sequence, + GtkGestureClick *click) +{ + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + g_assert (GTK_IS_GESTURE_CLICK (click)); + + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_ACTIVE); + gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static void +sysprof_tree_expander_expand (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + SysprofTreeExpander *self = (SysprofTreeExpander *)widget; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + + if (self->list_row != NULL) + gtk_tree_list_row_set_expanded (self->list_row, TRUE); +} + +static void +sysprof_tree_expander_collapse (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + SysprofTreeExpander *self = (SysprofTreeExpander *)widget; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + + if (self->list_row != NULL) + gtk_tree_list_row_set_expanded (self->list_row, FALSE); +} + +static void +sysprof_tree_expander_toggle_expand (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + SysprofTreeExpander *self = (SysprofTreeExpander *)widget; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + + if (self->list_row == NULL) + return; + + gtk_tree_list_row_set_expanded (self->list_row, + !gtk_tree_list_row_get_expanded (self->list_row)); +} + +static void +sysprof_tree_expander_dispose (GObject *object) +{ + SysprofTreeExpander *self = (SysprofTreeExpander *)object; + GtkWidget *child; + + sysprof_tree_expander_set_list_row (self, NULL); + + g_clear_pointer (&self->expander, gtk_widget_unparent); + g_clear_pointer (&self->image, gtk_widget_unparent); + g_clear_pointer (&self->child, gtk_widget_unparent); + g_clear_pointer (&self->suffix, gtk_widget_unparent); + + g_clear_object (&self->list_row); + g_clear_object (&self->menu_model); + + g_clear_object (&self->icon); + g_clear_object (&self->expanded_icon); + + child = gtk_widget_get_first_child (GTK_WIDGET (self)); + + while (child != NULL) + { + GtkWidget *cur = child; + child = gtk_widget_get_next_sibling (child); + if (GTK_IS_POPOVER (cur)) + gtk_widget_unparent (cur); + } + + G_OBJECT_CLASS (sysprof_tree_expander_parent_class)->dispose (object); +} + +static void +sysprof_tree_expander_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofTreeExpander *self = SYSPROF_TREE_EXPANDER (object); + + switch (prop_id) + { + case PROP_EXPANDED: + g_value_set_boolean (value, gtk_tree_list_row_get_expanded (self->list_row)); + break; + + case PROP_EXPANDED_ICON: + g_value_set_object (value, sysprof_tree_expander_get_expanded_icon (self)); + break; + + case PROP_ICON: + g_value_set_object (value, sysprof_tree_expander_get_icon (self)); + break; + + case PROP_ITEM: + g_value_take_object (value, sysprof_tree_expander_get_item (self)); + break; + + case PROP_LIST_ROW: + g_value_set_object (value, sysprof_tree_expander_get_list_row (self)); + break; + + case PROP_MENU_MODEL: + g_value_set_object (value, sysprof_tree_expander_get_menu_model (self)); + break; + + case PROP_SUFFIX: + g_value_set_object (value, sysprof_tree_expander_get_suffix (self)); + break; + + case PROP_CHILD: + g_value_set_object (value, sysprof_tree_expander_get_child (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_tree_expander_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofTreeExpander *self = SYSPROF_TREE_EXPANDER (object); + + switch (prop_id) + { + case PROP_EXPANDED_ICON: + sysprof_tree_expander_set_expanded_icon (self, g_value_get_object (value)); + break; + + case PROP_EXPANDED_ICON_NAME: + sysprof_tree_expander_set_expanded_icon_name (self, g_value_get_string (value)); + break; + + case PROP_ICON: + sysprof_tree_expander_set_icon (self, g_value_get_object (value)); + break; + + case PROP_ICON_NAME: + sysprof_tree_expander_set_icon_name (self, g_value_get_string (value)); + break; + + case PROP_LIST_ROW: + sysprof_tree_expander_set_list_row (self, g_value_get_object (value)); + break; + + case PROP_MENU_MODEL: + sysprof_tree_expander_set_menu_model (self, g_value_get_object (value)); + break; + + case PROP_SUFFIX: + sysprof_tree_expander_set_suffix (self, g_value_get_object (value)); + break; + + case PROP_CHILD: + sysprof_tree_expander_set_child (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_tree_expander_class_init (SysprofTreeExpanderClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = sysprof_tree_expander_dispose; + object_class->get_property = sysprof_tree_expander_get_property; + object_class->set_property = sysprof_tree_expander_set_property; + + properties [PROP_EXPANDED] = + g_param_spec_boolean ("expanded", NULL, NULL, + FALSE, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_EXPANDED_ICON] = + g_param_spec_object ("expanded-icon", NULL, NULL, + G_TYPE_ICON, + (G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_EXPANDED_ICON_NAME] = + g_param_spec_string ("expanded-icon-name", NULL, NULL, + NULL, + (G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_ICON] = + g_param_spec_object ("icon", NULL, NULL, + G_TYPE_ICON, + (G_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_STATIC_STRINGS)); + + properties[PROP_ICON_NAME] = + g_param_spec_string ("icon-name", NULL, NULL, + NULL, + (G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_LIST_ROW] = + g_param_spec_object ("list-row", NULL, NULL, + GTK_TYPE_TREE_LIST_ROW, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MENU_MODEL] = + g_param_spec_object ("menu-model", NULL, NULL, + G_TYPE_MENU_MODEL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_SUFFIX] = + g_param_spec_object ("suffix", NULL, NULL, + GTK_TYPE_WIDGET, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_CHILD] = + g_param_spec_object ("child", NULL, NULL, + GTK_TYPE_WIDGET, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); + gtk_widget_class_set_css_name (widget_class, "treeexpander"); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP); + + gtk_widget_class_install_action (widget_class, "listitem.toggle-expand", NULL, sysprof_tree_expander_toggle_expand); + gtk_widget_class_install_action (widget_class, "listitem.collapse", NULL, sysprof_tree_expander_collapse); + gtk_widget_class_install_action (widget_class, "listitem.expand", NULL, sysprof_tree_expander_expand); +} + +static void +sysprof_tree_expander_init (SysprofTreeExpander *self) +{ + GtkEventController *controller; + + self->expander = g_object_new (g_type_from_name ("GtkBuiltinIcon"), + "css-name", "expander", + NULL); + gtk_widget_insert_after (self->expander, GTK_WIDGET (self), NULL); + + self->image = g_object_new (GTK_TYPE_IMAGE, + "visible", FALSE, + NULL); + gtk_widget_insert_after (self->image, GTK_WIDGET (self), self->expander); + + controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ()); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0); + g_signal_connect_object (controller, + "pressed", + G_CALLBACK (sysprof_tree_expander_click_pressed_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (controller, + "released", + G_CALLBACK (sysprof_tree_expander_click_released_cb), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (controller, + "cancel", + G_CALLBACK (sysprof_tree_expander_click_cancel_cb), + self, + G_CONNECT_SWAPPED); + gtk_widget_add_controller (GTK_WIDGET (self), controller); +} + +GtkWidget * +sysprof_tree_expander_new (void) +{ + return g_object_new (SYSPROF_TYPE_TREE_EXPANDER, NULL); +} + +/** + * sysprof_tree_expander_get_item: + * @self: a #SysprofTreeExpander + * + * Gets the item instance from the model. + * + * Returns: (transfer full) (nullable) (type GObject): a #GObject or %NULL + */ +gpointer +sysprof_tree_expander_get_item (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + if (self->list_row == NULL) + return NULL; + + return gtk_tree_list_row_get_item (self->list_row); +} + +/** + * sysprof_tree_expander_get_menu_model: + * @self: a #SysprofTreeExpander + * + * Sets the menu model to use for context menus. + * + * Returns: (transfer none) (nullable): a #GMenuModel or %NULL + */ +GMenuModel * +sysprof_tree_expander_get_menu_model (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + return self->menu_model; +} + +void +sysprof_tree_expander_set_menu_model (SysprofTreeExpander *self, + GMenuModel *menu_model) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + g_return_if_fail (!menu_model || G_IS_MENU_MODEL (menu_model)); + + if (g_set_object (&self->menu_model, menu_model)) + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MENU_MODEL]); +} + +/** + * sysprof_tree_expander_get_icon: + * @self: a #SysprofTreeExpander + * + * Gets the icon for the row. + * + * Returns: (transfer none) (nullable): a #GIcon or %NULL + */ +GIcon * +sysprof_tree_expander_get_icon (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + return self->icon; +} + +/** + * sysprof_tree_expander_get_expanded_icon: + * @self: a #SysprofTreeExpander + * + * Gets the icon for the row when expanded. + * + * Returns: (transfer none) (nullable): a #GIcon or %NULL + */ +GIcon * +sysprof_tree_expander_get_expanded_icon (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + return self->expanded_icon; +} + +void +sysprof_tree_expander_set_icon (SysprofTreeExpander *self, + GIcon *icon) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + g_return_if_fail (!icon || G_IS_ICON (icon)); + + if (g_set_object (&self->icon, icon)) + { + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ICON]); + sysprof_tree_expander_update_icon (self); + } +} + +void +sysprof_tree_expander_set_expanded_icon (SysprofTreeExpander *self, + GIcon *expanded_icon) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + g_return_if_fail (!expanded_icon || G_IS_ICON (expanded_icon)); + + if (g_set_object (&self->expanded_icon, expanded_icon)) + { + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EXPANDED_ICON]); + sysprof_tree_expander_update_icon (self); + } +} + +void +sysprof_tree_expander_set_expanded_icon_name (SysprofTreeExpander *self, + const char *expanded_icon_name) +{ + g_autoptr(GIcon) expanded_icon = NULL; + + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + + if (expanded_icon_name != NULL) + expanded_icon = g_themed_icon_new (expanded_icon_name); + + sysprof_tree_expander_set_expanded_icon (self, expanded_icon); +} + +void +sysprof_tree_expander_set_icon_name (SysprofTreeExpander *self, + const char *icon_name) +{ + g_autoptr(GIcon) icon = NULL; + + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + + if (icon_name != NULL) + icon = g_themed_icon_new (icon_name); + + sysprof_tree_expander_set_icon (self, icon); +} + +/** + * sysprof_tree_expander_get_suffix: + * @self: a #SysprofTreeExpander + * + * Get the suffix widget, if any. + * + * Returns: (transfer none) (nullable): a #GtkWidget + */ +GtkWidget * +sysprof_tree_expander_get_suffix (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + return self->suffix; +} + +void +sysprof_tree_expander_set_suffix (SysprofTreeExpander *self, + GtkWidget *suffix) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + g_return_if_fail (!suffix || GTK_IS_WIDGET (suffix)); + + if (self->suffix == suffix) + return; + + g_clear_pointer (&self->suffix, gtk_widget_unparent); + + self->suffix = suffix; + + if (self->suffix) + gtk_widget_insert_before (suffix, GTK_WIDGET (self), NULL); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUFFIX]); +} + +GtkWidget * +sysprof_tree_expander_get_child (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + return self->child; +} + +void +sysprof_tree_expander_set_child (SysprofTreeExpander *self, + GtkWidget *child) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + + if (child == self->child) + return; + + if (child) + g_object_ref (child); + + g_clear_pointer (&self->child, gtk_widget_unparent); + + self->child = child; + + if (child) + gtk_widget_insert_after (child, GTK_WIDGET (self), self->image); + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILD]); +} + +/** + * sysprof_tree_expander_get_list_row: + * @self: a #SysprofTreeExpander + * + * Gets the list row for the expander. + * + * Returns: (transfer none) (nullable): a #GtkTreeListRow or %NULL + */ +GtkTreeListRow * +sysprof_tree_expander_get_list_row (SysprofTreeExpander *self) +{ + g_return_val_if_fail (SYSPROF_IS_TREE_EXPANDER (self), NULL); + + return self->list_row; +} + +static void +sysprof_tree_expander_clear_list_row (SysprofTreeExpander *self) +{ + GtkWidget *child; + + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + + if (self->list_row == NULL) + return; + + g_clear_signal_handler (&self->list_row_notify_expanded, self->list_row); + + g_clear_object (&self->list_row); + + gtk_image_set_from_icon_name (GTK_IMAGE (self->image), NULL); + + child = gtk_widget_get_prev_sibling (self->expander); + + while (child) + { + GtkWidget *prev = gtk_widget_get_prev_sibling (child); + gtk_widget_unparent (child); + child = prev; + } +} + +void +sysprof_tree_expander_set_list_row (SysprofTreeExpander *self, + GtkTreeListRow *list_row) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + g_return_if_fail (!list_row || GTK_IS_TREE_LIST_ROW (list_row)); + + if (self->list_row == list_row) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + sysprof_tree_expander_clear_list_row (self); + + if (list_row != NULL) + { + self->list_row = g_object_ref (list_row); + self->list_row_notify_expanded = + g_signal_connect_object (self->list_row, + "notify::expanded", + G_CALLBACK (sysprof_tree_expander_notify_expanded_cb), + self, + G_CONNECT_SWAPPED); + sysprof_tree_expander_update_depth (self); + sysprof_tree_expander_update_icon (self); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LIST_ROW]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ITEM]); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EXPANDED]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +static gboolean +sysprof_tree_expander_remove_popover_idle (gpointer user_data) +{ + gpointer *pair = user_data; + + g_assert (pair != NULL); + g_assert (SYSPROF_IS_TREE_EXPANDER (pair[0])); + g_assert (GTK_IS_POPOVER (pair[1])); + + if (gtk_widget_get_parent (pair[1]) == pair[0]) + gtk_widget_unparent (pair[1]); + + g_object_unref (pair[0]); + g_object_unref (pair[1]); + g_free (pair); + + return G_SOURCE_REMOVE; +} + +static void +sysprof_tree_expander_popover_closed_cb (SysprofTreeExpander *self, + GtkPopover *popover) +{ + g_assert (SYSPROF_IS_TREE_EXPANDER (self)); + g_assert (GTK_IS_POPOVER (popover)); + + if (self->popover == popover) + { + gpointer *pair = g_new (gpointer, 2); + pair[0] = g_object_ref (self); + pair[1] = g_object_ref (popover); + /* We don't want to unparent the widget immediately because it gets + * closed _BEFORE_ executing GAction. So removing it will cause the + * actions to be unavailable. + * + * Instead, defer to an idle where we remove the popover. + */ + g_idle_add (sysprof_tree_expander_remove_popover_idle, pair); + self->popover = NULL; + } +} + +void +sysprof_tree_expander_show_popover (SysprofTreeExpander *self, + GtkPopover *popover) +{ + g_return_if_fail (SYSPROF_IS_TREE_EXPANDER (self)); + g_return_if_fail (GTK_IS_POPOVER (popover)); + + gtk_widget_set_parent (GTK_WIDGET (popover), GTK_WIDGET (self)); + + g_signal_connect_object (popover, + "closed", + G_CALLBACK (sysprof_tree_expander_popover_closed_cb), + self, + G_CONNECT_SWAPPED); + + if (self->popover != NULL) + gtk_popover_popdown (self->popover); + + self->popover = popover; + + gtk_popover_popup (popover); +} diff --git a/src/sysprof/sysprof-tree-expander.h b/src/sysprof/sysprof-tree-expander.h new file mode 100644 index 00000000..2ebdf7c0 --- /dev/null +++ b/src/sysprof/sysprof-tree-expander.h @@ -0,0 +1,58 @@ +/* sysprof-tree-expander.h + * + * Copyright 2022 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_TREE_EXPANDER (sysprof_tree_expander_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofTreeExpander, sysprof_tree_expander, SYSPROF, TREE_EXPANDER, GtkWidget) + +GtkWidget *sysprof_tree_expander_new (void); +GMenuModel *sysprof_tree_expander_get_menu_model (SysprofTreeExpander *self); +void sysprof_tree_expander_set_menu_model (SysprofTreeExpander *self, + GMenuModel *menu_model); +GIcon *sysprof_tree_expander_get_icon (SysprofTreeExpander *self); +void sysprof_tree_expander_set_icon (SysprofTreeExpander *self, + GIcon *icon); +void sysprof_tree_expander_set_icon_name (SysprofTreeExpander *self, + const char *icon_name); +GIcon *sysprof_tree_expander_get_expanded_icon (SysprofTreeExpander *self); +void sysprof_tree_expander_set_expanded_icon (SysprofTreeExpander *self, + GIcon *icon); +void sysprof_tree_expander_set_expanded_icon_name (SysprofTreeExpander *self, + const char *expanded_icon_name); +GtkWidget *sysprof_tree_expander_get_child (SysprofTreeExpander *self); +void sysprof_tree_expander_set_child (SysprofTreeExpander *self, + GtkWidget *child); +GtkWidget *sysprof_tree_expander_get_suffix (SysprofTreeExpander *self); +void sysprof_tree_expander_set_suffix (SysprofTreeExpander *self, + GtkWidget *suffix); +GtkTreeListRow *sysprof_tree_expander_get_list_row (SysprofTreeExpander *self); +void sysprof_tree_expander_set_list_row (SysprofTreeExpander *self, + GtkTreeListRow *list_row); +gpointer sysprof_tree_expander_get_item (SysprofTreeExpander *self); +void sysprof_tree_expander_show_popover (SysprofTreeExpander *self, + GtkPopover *popover); + +G_END_DECLS diff --git a/src/sysprof/sysprof-value-axis.c b/src/sysprof/sysprof-value-axis.c new file mode 100644 index 00000000..0f054802 --- /dev/null +++ b/src/sysprof/sysprof-value-axis.c @@ -0,0 +1,247 @@ +/* sysprof-value-axis.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-axis-private.h" +#include "sysprof-value-axis.h" + +struct _SysprofValueAxis +{ + SysprofAxis parent_instance; + double min_value; + double max_value; + double distance; +}; + +struct _SysprofValueAxisClass +{ + SysprofAxisClass parent_class; +}; + +enum { + PROP_0, + PROP_MIN_VALUE, + PROP_MAX_VALUE, + N_PROPS +}; + +G_DEFINE_TYPE (SysprofValueAxis, sysprof_value_axis, SYSPROF_TYPE_AXIS) + +static GParamSpec *properties [N_PROPS]; + +static inline double +get_as_double (const GValue *value) +{ + if (G_VALUE_HOLDS_DOUBLE (value)) + return g_value_get_double (value); + + if (G_VALUE_HOLDS_FLOAT (value)) + return g_value_get_float (value); + + if (G_VALUE_HOLDS_INT64 (value)) + return g_value_get_int64 (value); + + if (G_VALUE_HOLDS_UINT64 (value)) + return g_value_get_uint64 (value); + + if (G_VALUE_HOLDS_INT (value)) + return g_value_get_int (value); + + if (G_VALUE_HOLDS_UINT (value)) + return g_value_get_uint (value); + + if (g_value_type_transformable (G_VALUE_TYPE (value), G_TYPE_DOUBLE)) + { + GValue dst = G_VALUE_INIT; + + g_value_init (&dst, G_TYPE_DOUBLE); + g_value_transform (value, &dst); + g_value_unset (&dst); + + return g_value_get_double (&dst); + } + + return .0; +} + +static void +sysprof_value_axis_real_get_min_value (SysprofAxis *axis, + GValue *value) +{ + SysprofValueAxis *self = SYSPROF_VALUE_AXIS (axis); + + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, MIN (self->min_value, self->max_value)); +} + +static double +sysprof_value_axis_normalize (SysprofAxis *axis, + const GValue *value) +{ + SysprofValueAxis *self = (SysprofValueAxis *)axis; + double v = get_as_double (value); + double r = (v - self->min_value) / self->distance; + + return r; +} + +static gboolean +sysprof_value_axis_is_pathological (SysprofAxis *axis) +{ + SysprofValueAxis *self = SYSPROF_VALUE_AXIS (axis); + + return self->min_value == self->max_value; +} + +static void +sysprof_value_axis_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofValueAxis *self = SYSPROF_VALUE_AXIS (object); + + switch (prop_id) + { + case PROP_MIN_VALUE: + g_value_set_double (value, sysprof_value_axis_get_min_value (self)); + break; + + case PROP_MAX_VALUE: + g_value_set_double (value, sysprof_value_axis_get_max_value (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_value_axis_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofValueAxis *self = SYSPROF_VALUE_AXIS (object); + + switch (prop_id) + { + case PROP_MIN_VALUE: + sysprof_value_axis_set_min_value (self, g_value_get_double (value)); + break; + + case PROP_MAX_VALUE: + sysprof_value_axis_set_max_value (self, g_value_get_double (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_value_axis_class_init (SysprofValueAxisClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofAxisClass *axis_class = SYSPROF_AXIS_CLASS (klass); + + object_class->get_property = sysprof_value_axis_get_property; + object_class->set_property = sysprof_value_axis_set_property; + + axis_class->get_min_value = sysprof_value_axis_real_get_min_value; + axis_class->normalize = sysprof_value_axis_normalize; + axis_class->is_pathological = sysprof_value_axis_is_pathological; + + properties[PROP_MIN_VALUE] = + g_param_spec_double ("min-value", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_MAX_VALUE] = + g_param_spec_double ("max-value", NULL, NULL, + -G_MAXDOUBLE, G_MAXDOUBLE, 0, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_value_axis_init (SysprofValueAxis *self) +{ +} + +double +sysprof_value_axis_get_min_value (SysprofValueAxis *self) +{ + g_return_val_if_fail (SYSPROF_IS_VALUE_AXIS (self), .0); + + return self->min_value; +} + +void +sysprof_value_axis_set_min_value (SysprofValueAxis *self, + double min_value) +{ + g_return_if_fail (SYSPROF_IS_VALUE_AXIS (self)); + + if (min_value != self->min_value) + { + self->min_value = min_value; + self->distance = self->max_value - self->min_value; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_VALUE]); + _sysprof_axis_emit_range_changed (SYSPROF_AXIS (self)); + } +} + +double +sysprof_value_axis_get_max_value (SysprofValueAxis *self) +{ + g_return_val_if_fail (SYSPROF_IS_VALUE_AXIS (self), .0); + + return self->max_value; +} + +void +sysprof_value_axis_set_max_value (SysprofValueAxis *self, + double max_value) +{ + g_return_if_fail (SYSPROF_IS_VALUE_AXIS (self)); + + if (max_value != self->max_value) + { + self->max_value = max_value; + self->distance = self->max_value - self->min_value; + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_VALUE]); + _sysprof_axis_emit_range_changed (SYSPROF_AXIS (self)); + } +} + +SysprofAxis * +sysprof_value_axis_new (double min_value, + double max_value) +{ + return g_object_new (SYSPROF_TYPE_VALUE_AXIS, + "min-value", min_value, + "max-value", max_value, + NULL); +} diff --git a/src/sysprof/sysprof-value-axis.h b/src/sysprof/sysprof-value-axis.h new file mode 100644 index 00000000..80e3dba3 --- /dev/null +++ b/src/sysprof/sysprof-value-axis.h @@ -0,0 +1,47 @@ +/* sysprof-value-axis.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-axis.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_VALUE_AXIS (sysprof_value_axis_get_type()) +#define SYSPROF_IS_VALUE_AXIS(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_VALUE_AXIS)) +#define SYSPROF_VALUE_AXIS(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_VALUE_AXIS, SysprofValueAxis)) +#define SYSPROF_VALUE_AXIS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_VALUE_AXIS, SysprofValueAxisClass)) + +typedef struct _SysprofValueAxis SysprofValueAxis; +typedef struct _SysprofValueAxisClass SysprofValueAxisClass; + +GType sysprof_value_axis_get_type (void) G_GNUC_CONST; +SysprofAxis *sysprof_value_axis_new (double min_value, + double max_value); +double sysprof_value_axis_get_min_value (SysprofValueAxis *self); +void sysprof_value_axis_set_min_value (SysprofValueAxis *self, + double min_value); +double sysprof_value_axis_get_max_value (SysprofValueAxis *self); +void sysprof_value_axis_set_max_value (SysprofValueAxis *self, + double max_value); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofValueAxis, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-weighted-callgraph-view.c b/src/sysprof/sysprof-weighted-callgraph-view.c new file mode 100644 index 00000000..c2b4dacb --- /dev/null +++ b/src/sysprof/sysprof-weighted-callgraph-view.c @@ -0,0 +1,348 @@ +/* sysprof-weighted-callgraph-view.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-callgraph-view-private.h" +#include "sysprof-weighted-callgraph-view.h" +#include "sysprof-progress-cell-private.h" + +struct _SysprofWeightedCallgraphView +{ + SysprofCallgraphView parent_instance; + + GtkColumnViewColumn *callers_self_column; + GtkColumnViewColumn *callers_total_column; + GtkCustomSorter *callers_self_sorter; + GtkCustomSorter *callers_total_sorter; + + GtkColumnViewColumn *descendants_self_column; + GtkColumnViewColumn *descendants_total_column; + GtkCustomSorter *descendants_self_sorter; + GtkCustomSorter *descendants_total_sorter; + + GtkColumnViewColumn *functions_self_column; + GtkColumnViewColumn *functions_total_column; + GtkCustomSorter *functions_self_sorter; + GtkCustomSorter *functions_total_sorter; +}; + +struct _SysprofWeightedCallgraphViewClass +{ + SysprofCallgraphViewClass parent_class; +}; + +typedef struct _AugmentWeight +{ + guint size; + guint total; +} AugmentWeight; + +G_DEFINE_FINAL_TYPE (SysprofWeightedCallgraphView, sysprof_weighted_callgraph_view, SYSPROF_TYPE_CALLGRAPH_VIEW) + +static void +augment_weight (SysprofCallgraph *callgraph, + SysprofCallgraphNode *node, + SysprofDocumentFrame *frame, + gboolean summarize, + gpointer user_data) +{ + AugmentWeight *cur; + AugmentWeight *sum; + + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + g_assert (node != NULL); + g_assert (SYSPROF_IS_DOCUMENT_SAMPLE (frame)); + g_assert (user_data == NULL); + + cur = sysprof_callgraph_get_augment (callgraph, node); + cur->size += 1; + cur->total += 1; + + if (summarize) + { + sum = sysprof_callgraph_get_summary_augment (callgraph, node); + sum->size += 1; + sum->total += 1; + } + + for (node = sysprof_callgraph_node_parent (node); + node != NULL; + node = sysprof_callgraph_node_parent (node)) + { + cur = sysprof_callgraph_get_augment (callgraph, node); + cur->total += 1; + + if (summarize) + { + sum = sysprof_callgraph_get_summary_augment (callgraph, node); + sum->total += 1; + } + } +} + +static double +get_total_fraction (GObject *item) +{ + g_autoptr(GObject) row = NULL; + + g_object_get (item, "item", &row, NULL); + + if (GTK_IS_TREE_LIST_ROW (row)) + { + GtkTreeListRow *tree_row = GTK_TREE_LIST_ROW (row); + SysprofCallgraphFrame *frame = SYSPROF_CALLGRAPH_FRAME (gtk_tree_list_row_get_item (tree_row)); + SysprofCallgraph *callgraph = sysprof_callgraph_frame_get_callgraph (frame); + AugmentWeight *sum = sysprof_callgraph_frame_get_augment (frame); + AugmentWeight *root = sysprof_callgraph_get_augment (callgraph, NULL); + + return sum->total / (double)root->total; + } + + return 0; +} + +static double +get_self_fraction (GObject *item) +{ + g_autoptr(GObject) row = NULL; + + g_object_get (item, "item", &row, NULL); + + if (GTK_IS_TREE_LIST_ROW (row)) + { + GtkTreeListRow *tree_row = GTK_TREE_LIST_ROW (row); + SysprofCallgraphFrame *frame = SYSPROF_CALLGRAPH_FRAME (gtk_tree_list_row_get_item (tree_row)); + SysprofCallgraph *callgraph = sysprof_callgraph_frame_get_callgraph (frame); + AugmentWeight *sum = sysprof_callgraph_frame_get_augment (frame); + AugmentWeight *root = sysprof_callgraph_get_augment (callgraph, NULL); + + return sum->size / (double)root->total; + } + + return .0; +} + +static double +functions_get_total_fraction (GObject *item) +{ + g_autoptr(SysprofCallgraphSymbol) sym = NULL; + + g_object_get (item, "item", &sym, NULL); + + if (SYSPROF_IS_CALLGRAPH_SYMBOL (sym)) + { + SysprofCallgraph *callgraph = sysprof_callgraph_symbol_get_callgraph (sym); + AugmentWeight *sum = sysprof_callgraph_symbol_get_summary_augment (sym); + AugmentWeight *root = sysprof_callgraph_get_augment (callgraph, NULL); + + return sum->total / (double)root->total; + } + + return 0; +} + +static double +functions_get_self_fraction (GObject *item) +{ + g_autoptr(SysprofCallgraphSymbol) sym = NULL; + + g_object_get (item, "item", &sym, NULL); + + if (SYSPROF_IS_CALLGRAPH_SYMBOL (sym)) + { + SysprofCallgraph *callgraph = sysprof_callgraph_symbol_get_callgraph (sym); + AugmentWeight *sum = sysprof_callgraph_symbol_get_summary_augment (sym); + AugmentWeight *root = sysprof_callgraph_get_augment (callgraph, NULL); + + return sum->size / (double)root->total; + } + + return 0; +} + +static int +descendants_sort_by_self (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; + SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; + AugmentWeight *aug_a = sysprof_callgraph_frame_get_augment (frame_a); + AugmentWeight *aug_b = sysprof_callgraph_frame_get_augment (frame_b); + AugmentWeight *root = user_data; + double self_a = aug_a->size / (double)root->total; + double self_b = aug_b->size / (double)root->total; + + if (self_a < self_b) + return -1; + else if (self_a > self_b) + return 1; + else + return 0; +} + +static int +descendants_sort_by_total (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphFrame *frame_a = (SysprofCallgraphFrame *)a; + SysprofCallgraphFrame *frame_b = (SysprofCallgraphFrame *)b; + AugmentWeight *aug_a = sysprof_callgraph_frame_get_augment (frame_a); + AugmentWeight *aug_b = sysprof_callgraph_frame_get_augment (frame_b); + AugmentWeight *root = user_data; + double total_a = aug_a->total / (double)root->total; + double total_b = aug_b->total / (double)root->total; + + if (total_a < total_b) + return -1; + else if (total_a > total_b) + return 1; + else + return 0; +} + +static int +functions_sort_by_self (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphSymbol *sym_a = (SysprofCallgraphSymbol *)a; + SysprofCallgraphSymbol *sym_b = (SysprofCallgraphSymbol *)b; + AugmentWeight *aug_a = sysprof_callgraph_symbol_get_summary_augment (sym_a); + AugmentWeight *aug_b = sysprof_callgraph_symbol_get_summary_augment (sym_b); + AugmentWeight *root = user_data; + double self_a = aug_a->size / (double)root->total; + double self_b = aug_b->size / (double)root->total; + + if (self_a < self_b) + return -1; + else if (self_a > self_b) + return 1; + else + return 0; +} + +static int +functions_sort_by_total (gconstpointer a, + gconstpointer b, + gpointer user_data) +{ + SysprofCallgraphSymbol *sym_a = (SysprofCallgraphSymbol *)a; + SysprofCallgraphSymbol *sym_b = (SysprofCallgraphSymbol *)b; + AugmentWeight *aug_a = sysprof_callgraph_symbol_get_summary_augment (sym_a); + AugmentWeight *aug_b = sysprof_callgraph_symbol_get_summary_augment (sym_b); + AugmentWeight *root = user_data; + double total_a = aug_a->total / (double)root->total; + double total_b = aug_b->total / (double)root->total; + + if (total_a < total_b) + return -1; + else if (total_a > total_b) + return 1; + else + return 0; +} + +static void +sysprof_weighted_callgraph_view_load (SysprofCallgraphView *view, + SysprofCallgraph *callgraph) +{ + SysprofWeightedCallgraphView *self = (SysprofWeightedCallgraphView *)view; + AugmentWeight *root; + + g_assert (SYSPROF_IS_WEIGHTED_CALLGRAPH_VIEW (self)); + g_assert (SYSPROF_IS_CALLGRAPH (callgraph)); + + root = sysprof_callgraph_get_augment (callgraph, NULL); + + gtk_custom_sorter_set_sort_func (self->descendants_self_sorter, + descendants_sort_by_self, root, NULL); + gtk_custom_sorter_set_sort_func (self->descendants_total_sorter, + descendants_sort_by_total, root, NULL); + + gtk_custom_sorter_set_sort_func (self->functions_self_sorter, + functions_sort_by_self, root, NULL); + gtk_custom_sorter_set_sort_func (self->functions_total_sorter, + functions_sort_by_total, root, NULL); + + gtk_custom_sorter_set_sort_func (self->callers_self_sorter, + functions_sort_by_self, root, NULL); + gtk_custom_sorter_set_sort_func (self->callers_total_sorter, + functions_sort_by_total, root, NULL); + + gtk_column_view_sort_by_column (SYSPROF_CALLGRAPH_VIEW (self)->callers_column_view, + self->callers_total_column, + GTK_SORT_DESCENDING); + gtk_column_view_sort_by_column (SYSPROF_CALLGRAPH_VIEW (self)->descendants_column_view, + self->descendants_total_column, + GTK_SORT_DESCENDING); + gtk_column_view_sort_by_column (SYSPROF_CALLGRAPH_VIEW (self)->functions_column_view, + self->functions_total_column, + GTK_SORT_DESCENDING); +} + +static void +sysprof_weighted_callgraph_view_class_init (SysprofWeightedCallgraphViewClass *klass) +{ + SysprofCallgraphViewClass *callgraph_view_class = SYSPROF_CALLGRAPH_VIEW_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + callgraph_view_class->augment_size = sizeof (AugmentWeight); + callgraph_view_class->augment_func = augment_weight; + callgraph_view_class->load = sysprof_weighted_callgraph_view_load; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-weighted-callgraph-view.ui"); + + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, callers_self_column); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, callers_total_column); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, callers_self_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, callers_total_sorter); + + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, descendants_self_column); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, descendants_total_column); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, descendants_self_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, descendants_total_sorter); + + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, functions_self_column); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, functions_total_column); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, functions_self_sorter); + gtk_widget_class_bind_template_child (widget_class, SysprofWeightedCallgraphView, functions_total_sorter); + + gtk_widget_class_bind_template_callback (widget_class, get_self_fraction); + gtk_widget_class_bind_template_callback (widget_class, get_total_fraction); + gtk_widget_class_bind_template_callback (widget_class, functions_get_self_fraction); + gtk_widget_class_bind_template_callback (widget_class, functions_get_total_fraction); + + g_type_ensure (SYSPROF_TYPE_PROGRESS_CELL); +} + +static void +sysprof_weighted_callgraph_view_init (SysprofWeightedCallgraphView *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget * +sysprof_weighted_callgraph_view_new (void) +{ + return g_object_new (SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW, NULL); +} diff --git a/src/sysprof/sysprof-weighted-callgraph-view.h b/src/sysprof/sysprof-weighted-callgraph-view.h new file mode 100644 index 00000000..67c2f3f4 --- /dev/null +++ b/src/sysprof/sysprof-weighted-callgraph-view.h @@ -0,0 +1,40 @@ +/* sysprof-weighted-callgraph-view.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-callgraph-view.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW (sysprof_weighted_callgraph_view_get_type()) +#define SYSPROF_IS_WEIGHTED_CALLGRAPH_VIEW(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW) +#define SYSPROF_WEIGHTED_CALLGRAPH_VIEW(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW, SysprofWeightedCallgraphView) +#define SYSPROF_WEIGHTED_CALLGRAPH_VIEW_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_WEIGHTED_CALLGRAPH_VIEW, SysprofWeightedCallgraphViewClass) + +typedef struct _SysprofWeightedCallgraphView SysprofWeightedCallgraphView; +typedef struct _SysprofWeightedCallgraphViewClass SysprofWeightedCallgraphViewClass; + +GType sysprof_weighted_callgraph_view_get_type (void) G_GNUC_CONST; +GtkWidget *sysprof_weighted_weighted_callgraph_view_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofWeightedCallgraphView, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof-weighted-callgraph-view.ui b/src/sysprof/sysprof-weighted-callgraph-view.ui new file mode 100644 index 00000000..3d9c34c4 --- /dev/null +++ b/src/sysprof/sysprof-weighted-callgraph-view.ui @@ -0,0 +1,203 @@ + + + + diff --git a/src/sysprof/sysprof-window.c b/src/sysprof/sysprof-window.c index 2da8b091..e58613e2 100644 --- a/src/sysprof/sysprof-window.c +++ b/src/sysprof/sysprof-window.c @@ -1,6 +1,6 @@ /* sysprof-window.c * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,167 +18,348 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -#define G_LOG_DOMAIN "sysprof-window" - #include "config.h" #include -#include +#include "sysprof-counters-section.h" +#include "sysprof-cpu-section.h" +#include "sysprof-files-section.h" +#include "sysprof-greeter.h" +#include "sysprof-logs-section.h" +#include "sysprof-marks-section.h" +#include "sysprof-memory-section.h" +#include "sysprof-metadata-section.h" +#include "sysprof-processes-section.h" +#include "sysprof-samples-section.h" +#include "sysprof-sidebar.h" #include "sysprof-window.h" struct _SysprofWindow { AdwApplicationWindow parent_instance; - GBindingGroup *bindings; + SysprofDocument *document; + SysprofSession *session; - SysprofNotebook *notebook; - GtkButton *open_button; - GtkMenuButton *menu_button; + GtkToggleButton *show_right_sidebar; + GtkWidget *left_split_overlay; + GtkWidget *right_split_overlay; + + guint disposed : 1; }; -G_DEFINE_TYPE (SysprofWindow, sysprof_window, ADW_TYPE_APPLICATION_WINDOW) +enum { + PROP_0, + PROP_DOCUMENT, + PROP_SESSION, + N_PROPS +}; -/** - * sysprof_window_new: - * - * Create a new #SysprofWindow. - * - * Returns: (transfer full): a newly created #SysprofWindow - */ -GtkWidget * -sysprof_window_new (SysprofApplication *application) -{ - return g_object_new (SYSPROF_TYPE_WINDOW, - "application", application, - NULL); -} +G_DEFINE_FINAL_TYPE (SysprofWindow, sysprof_window, ADW_TYPE_APPLICATION_WINDOW) + +static GParamSpec *properties [N_PROPS]; static void -sysprof_window_notify_can_replay_cb (SysprofWindow *self, - GParamSpec *pspec, - SysprofNotebook *notebook) +sysprof_window_update_zoom_actions (SysprofWindow *self) { + const SysprofTimeSpan *visible_time; + const SysprofTimeSpan *document_time; + g_assert (SYSPROF_IS_WINDOW (self)); - g_assert (SYSPROF_IS_NOTEBOOK (notebook)); + + if (self->session == NULL) + return; + + visible_time = sysprof_session_get_visible_time (self->session); + document_time = sysprof_session_get_document_time (self->session); gtk_widget_action_set_enabled (GTK_WIDGET (self), - "win.replay-capture", - sysprof_notebook_get_can_replay (notebook)); -} - -static void -sysprof_window_notify_can_save_cb (SysprofWindow *self, - GParamSpec *pspec, - SysprofNotebook *notebook) -{ - g_assert (SYSPROF_IS_WINDOW (self)); - g_assert (SYSPROF_IS_NOTEBOOK (notebook)); - + "session.zoom-one", + !sysprof_time_span_equal (visible_time, document_time)); gtk_widget_action_set_enabled (GTK_WIDGET (self), - "win.save-capture", - sysprof_notebook_get_can_save (notebook)); + "session.zoom-out", + !sysprof_time_span_equal (visible_time, document_time)); + gtk_widget_action_set_enabled (GTK_WIDGET (self), + "session.seek-backward", + visible_time->begin_nsec > document_time->begin_nsec); + gtk_widget_action_set_enabled (GTK_WIDGET (self), + "session.seek-forward", + visible_time->end_nsec < document_time->end_nsec); } static void -new_tab_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) +show_greeter (SysprofWindow *self, + SysprofGreeterPage page) { - SysprofWindow *self = (SysprofWindow *)widget; + SysprofGreeter *greeter; - g_return_if_fail (SYSPROF_IS_WINDOW (self)); + g_assert (SYSPROF_IS_WINDOW (self)); - sysprof_window_new_tab (self); + greeter = g_object_new (SYSPROF_TYPE_GREETER, + "transient-for", self, + NULL); + sysprof_greeter_set_page (greeter, page); + gtk_window_present (GTK_WINDOW (greeter)); } static void -switch_tab_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) +sysprof_window_open_capture_action (GtkWidget *widget, + const char *action_name, + GVariant *param) { - SysprofWindow *self = (SysprofWindow *)widget; - int page; - - g_return_if_fail (SYSPROF_IS_WINDOW (self)); - g_return_if_fail (g_variant_is_of_type (param, G_VARIANT_TYPE_INT32)); - - page = g_variant_get_int32 (param); - sysprof_notebook_set_current_page (self->notebook, page - 1); + show_greeter (SYSPROF_WINDOW (widget), SYSPROF_GREETER_PAGE_OPEN); } static void -close_tab_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) +sysprof_window_record_capture_action (GtkWidget *widget, + const char *action_name, + GVariant *param) { - SysprofWindow *self = (SysprofWindow *)widget; + show_greeter (SYSPROF_WINDOW (widget), SYSPROF_GREETER_PAGE_RECORD); +} - g_return_if_fail (SYSPROF_IS_WINDOW (self)); +static void +sysprof_window_set_document (SysprofWindow *self, + SysprofDocument *document) +{ + static const char *callgraph_actions[] = { + "bottom-up", + "categorize-frames", + "hide-system-libraries", + "include-threads", + }; - if (sysprof_notebook_get_n_pages (self->notebook) == 1) + g_assert (SYSPROF_IS_WINDOW (self)); + g_assert (SYSPROF_IS_DOCUMENT (document)); + g_assert (self->document == NULL); + g_assert (self->session == NULL); + + g_set_object (&self->document, document); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DOCUMENT]); + + self->session = sysprof_session_new (document); + g_signal_connect_object (self->session, + "notify::selected-time", + G_CALLBACK (sysprof_window_update_zoom_actions), + self, + G_CONNECT_SWAPPED); + g_signal_connect_object (self->session, + "notify::visible-time", + G_CALLBACK (sysprof_window_update_zoom_actions), + self, + G_CONNECT_SWAPPED); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SESSION]); + + for (guint i = 0; i < G_N_ELEMENTS (callgraph_actions); i++) { - SysprofDisplay *child = sysprof_notebook_get_nth_page (self->notebook, 0); + g_autofree char *action_name = g_strdup_printf ("callgraph.%s", callgraph_actions[i]); + g_autoptr(GPropertyAction) action = g_property_action_new (action_name, self->session, callgraph_actions[i]); - if (SYSPROF_IS_DISPLAY (child) && - sysprof_display_is_empty (SYSPROF_DISPLAY (child))) - { - gtk_window_destroy (GTK_WINDOW (self)); - return; - } + g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (action)); } - sysprof_notebook_close_current (self->notebook); + sysprof_window_update_zoom_actions (self); } static void -replay_capture_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) +main_view_notify_sidebar (SysprofWindow *self, + GParamSpec *pspec, + AdwOverlaySplitView *main_view) { - SysprofWindow *self = (SysprofWindow *)widget; + GtkWidget *sidebar; - g_return_if_fail (SYSPROF_IS_WINDOW (self)); + g_assert (SYSPROF_IS_WINDOW (self)); + g_assert (ADW_IS_OVERLAY_SPLIT_VIEW (main_view)); - sysprof_notebook_replay (self->notebook); + if (self->disposed) + return; + + sidebar = adw_overlay_split_view_get_sidebar (main_view); + + if (sidebar == NULL) + adw_overlay_split_view_set_show_sidebar (main_view, FALSE); + + gtk_widget_set_sensitive (GTK_WIDGET (self->show_right_sidebar), sidebar != NULL); } static void -save_capture_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) +sysprof_window_session_seek_backward (GtkWidget *widget, + const char *action_name, + GVariant *param) { SysprofWindow *self = (SysprofWindow *)widget; - - g_return_if_fail (SYSPROF_IS_WINDOW (self)); - - sysprof_notebook_save (self->notebook); -} - -static void -stop_recording_cb (GtkWidget *widget, - const char *action_name, - GVariant *param) -{ - SysprofWindow *self = (SysprofWindow *)widget; - SysprofDisplay *current; + const SysprofTimeSpan *document_time; + const SysprofTimeSpan *visible_time; + SysprofTimeSpan select; + gint64 duration; g_assert (SYSPROF_IS_WINDOW (self)); - if ((current = sysprof_notebook_get_current (self->notebook))) - sysprof_display_stop_recording (current); + if (self->session == NULL) + return; + + visible_time = sysprof_session_get_visible_time (self->session); + document_time = sysprof_session_get_document_time (self->session); + duration = sysprof_time_span_duration (*visible_time); + + select.begin_nsec = MAX (document_time->begin_nsec, visible_time->begin_nsec - duration); + select.end_nsec = select.begin_nsec + duration; + + sysprof_session_select_time (self->session, &select); + sysprof_session_zoom_to_selection (self->session); } static void -sysprof_window_finalize (GObject *object) +sysprof_window_session_seek_forward (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofWindow *self = (SysprofWindow *)widget; + const SysprofTimeSpan *document_time; + const SysprofTimeSpan *visible_time; + SysprofTimeSpan select; + gint64 duration; + + g_assert (SYSPROF_IS_WINDOW (self)); + + if (self->session == NULL) + return; + + visible_time = sysprof_session_get_visible_time (self->session); + document_time = sysprof_session_get_document_time (self->session); + duration = sysprof_time_span_duration (*visible_time); + + select.begin_nsec = MIN (document_time->end_nsec - duration, visible_time->begin_nsec + duration); + select.end_nsec = select.begin_nsec + duration; + + sysprof_session_select_time (self->session, &select); + sysprof_session_zoom_to_selection (self->session); +} + +static void +sysprof_window_session_zoom_one (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofWindow *self = (SysprofWindow *)widget; + + g_assert (SYSPROF_IS_WINDOW (self)); + + if (self->session == NULL) + return; + + sysprof_session_select_time (self->session, + sysprof_session_get_document_time (self->session)); +} + +static void +sysprof_window_session_zoom_in (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofWindow *self = (SysprofWindow *)widget; + const SysprofTimeSpan *visible_time; + SysprofTimeSpan select; + gint64 duration; + + g_assert (SYSPROF_IS_WINDOW (self)); + + if (self->session == NULL) + return; + + visible_time = sysprof_session_get_visible_time (self->session); + duration = sysprof_time_span_duration (*visible_time); + + select.begin_nsec = visible_time->begin_nsec + (duration / 4); + select.end_nsec = select.begin_nsec + (duration / 2); + + sysprof_session_select_time (self->session, &select); + sysprof_session_zoom_to_selection (self->session); +} + +static void +sysprof_window_session_zoom_out (GtkWidget *widget, + const char *action_name, + GVariant *param) +{ + SysprofWindow *self = (SysprofWindow *)widget; + SysprofTimeSpan select; + gint64 duration; + + g_assert (SYSPROF_IS_WINDOW (self)); + + if (self->session == NULL) + return; + + select = *sysprof_session_get_visible_time (self->session); + duration = sysprof_time_span_duration (select); + + select.begin_nsec -= floor (duration / 2.); + select.end_nsec += ceil (duration / 2.); + + sysprof_session_select_time (self->session, &select); + sysprof_session_zoom_to_selection (self->session); +} + +static void +sysprof_window_dispose (GObject *object) { SysprofWindow *self = (SysprofWindow *)object; - g_binding_group_set_source (self->bindings, NULL); - g_clear_object (&self->bindings); + self->disposed = TRUE; - G_OBJECT_CLASS (sysprof_window_parent_class)->finalize (object); + if (self->right_split_overlay) + adw_overlay_split_view_set_sidebar (ADW_OVERLAY_SPLIT_VIEW (self->right_split_overlay), NULL); + + gtk_widget_dispose_template (GTK_WIDGET (self), SYSPROF_TYPE_WINDOW); + + g_clear_object (&self->document); + g_clear_object (&self->session); + + G_OBJECT_CLASS (sysprof_window_parent_class)->dispose (object); +} + +static void +sysprof_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofWindow *self = SYSPROF_WINDOW (object); + + switch (prop_id) + { + case PROP_DOCUMENT: + g_value_set_object (value, sysprof_window_get_document (self)); + break; + + case PROP_SESSION: + g_value_set_object (value, sysprof_window_get_session (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_window_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofWindow *self = SYSPROF_WINDOW (object); + + switch (prop_id) + { + case PROP_DOCUMENT: + sysprof_window_set_document (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } } static void @@ -187,131 +368,215 @@ sysprof_window_class_init (SysprofWindowClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); - object_class->finalize = sysprof_window_finalize; + object_class->dispose = sysprof_window_dispose; + object_class->get_property = sysprof_window_get_property; + object_class->set_property = sysprof_window_set_property; - gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/ui/sysprof-window.ui"); - gtk_widget_class_bind_template_child (widget_class, SysprofWindow, menu_button); - gtk_widget_class_bind_template_child (widget_class, SysprofWindow, open_button); - gtk_widget_class_bind_template_child (widget_class, SysprofWindow, notebook); + properties[PROP_DOCUMENT] = + g_param_spec_object ("document", NULL, NULL, + SYSPROF_TYPE_DOCUMENT, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - gtk_widget_class_install_action (widget_class, "win.close-tab", NULL, close_tab_cb); - gtk_widget_class_install_action (widget_class, "win.new-tab", NULL, new_tab_cb); - gtk_widget_class_install_action (widget_class, "win.switch-tab", "i", switch_tab_cb); - gtk_widget_class_install_action (widget_class, "win.replay-capture", NULL, replay_capture_cb); - gtk_widget_class_install_action (widget_class, "win.save-capture", NULL, save_capture_cb); - gtk_widget_class_install_action (widget_class, "win.stop-recording", NULL, stop_recording_cb); + properties[PROP_SESSION] = + g_param_spec_object ("session", NULL, NULL, + SYSPROF_TYPE_SESSION, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); - gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "win.stop-recording", NULL); + g_object_class_install_properties (object_class, N_PROPS, properties); - g_type_ensure (SYSPROF_TYPE_NOTEBOOK); - g_type_ensure (SYSPROF_TYPE_DISPLAY); + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/sysprof/sysprof-window.ui"); + + gtk_widget_class_bind_template_child (widget_class, SysprofWindow, show_right_sidebar); + gtk_widget_class_bind_template_child (widget_class, SysprofWindow, left_split_overlay); + gtk_widget_class_bind_template_child (widget_class, SysprofWindow, right_split_overlay); + + gtk_widget_class_bind_template_callback (widget_class, main_view_notify_sidebar); + + gtk_widget_class_install_action (widget_class, "win.open-capture", NULL, sysprof_window_open_capture_action); + gtk_widget_class_install_action (widget_class, "win.record-capture", NULL, sysprof_window_record_capture_action); + gtk_widget_class_install_action (widget_class, "session.zoom-one", NULL, sysprof_window_session_zoom_one); + gtk_widget_class_install_action (widget_class, "session.zoom-out", NULL, sysprof_window_session_zoom_out); + gtk_widget_class_install_action (widget_class, "session.zoom-in", NULL, sysprof_window_session_zoom_in); + gtk_widget_class_install_action (widget_class, "session.seek-forward", NULL, sysprof_window_session_seek_forward); + gtk_widget_class_install_action (widget_class, "session.seek-backward", NULL, sysprof_window_session_seek_backward); + + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_plus, GDK_CONTROL_MASK, "session.zoom-in", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_equal, GDK_CONTROL_MASK, "session.zoom-in", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_minus, GDK_CONTROL_MASK, "session.zoom-out", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_0, GDK_CONTROL_MASK, "session.zoom-one", NULL); + + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_bracketleft, GDK_CONTROL_MASK, "session.seek-backward", NULL); + gtk_widget_class_add_binding_action (widget_class, GDK_KEY_bracketright, GDK_CONTROL_MASK, "session.seek-forward", NULL); + + g_type_ensure (SYSPROF_TYPE_COUNTERS_SECTION); + g_type_ensure (SYSPROF_TYPE_CPU_SECTION); + g_type_ensure (SYSPROF_TYPE_DOCUMENT); + g_type_ensure (SYSPROF_TYPE_FILES_SECTION); + g_type_ensure (SYSPROF_TYPE_LOGS_SECTION); + g_type_ensure (SYSPROF_TYPE_MARKS_SECTION); + g_type_ensure (SYSPROF_TYPE_MEMORY_SECTION); + g_type_ensure (SYSPROF_TYPE_METADATA_SECTION); + g_type_ensure (SYSPROF_TYPE_PROCESSES_SECTION); + g_type_ensure (SYSPROF_TYPE_SAMPLES_SECTION); + g_type_ensure (SYSPROF_TYPE_SESSION); + g_type_ensure (SYSPROF_TYPE_SIDEBAR); } static void sysprof_window_init (SysprofWindow *self) { - GMenu *menu; + g_autoptr(GPropertyAction) show_left_sidebar = NULL; + g_autoptr(GPropertyAction) show_right_sidebar = NULL; gtk_widget_init_template (GTK_WIDGET (self)); - menu = gtk_application_get_menu_by_id (GTK_APPLICATION (g_application_get_default ()), "win-menu"); - gtk_menu_button_set_menu_model (self->menu_button, G_MENU_MODEL (menu)); + show_left_sidebar = g_property_action_new ("show-left-sidebar", + self->left_split_overlay, + "show-sidebar"); + g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (show_left_sidebar)); - g_signal_connect_object (self->notebook, - "notify::can-replay", - G_CALLBACK (sysprof_window_notify_can_replay_cb), - self, - G_CONNECT_SWAPPED); - - g_signal_connect_object (self->notebook, - "notify::can-save", - G_CALLBACK (sysprof_window_notify_can_save_cb), - self, - G_CONNECT_SWAPPED); - - self->bindings = g_binding_group_new (); - g_binding_group_bind (self->bindings, "title", self, "title", G_BINDING_SYNC_CREATE); - g_object_bind_property (self->notebook, "current", self->bindings, "source", - G_BINDING_SYNC_CREATE); - - gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.save-capture", FALSE); - gtk_widget_action_set_enabled (GTK_WIDGET (self), "win.replay-capture", FALSE); + show_right_sidebar = g_property_action_new ("show-right-sidebar", + self->right_split_overlay, + "show-sidebar"); + g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (show_right_sidebar)); } -void -sysprof_window_open (SysprofWindow *self, - GFile *file) +GtkWidget * +sysprof_window_new (SysprofApplication *app, + SysprofDocument *document) { - g_return_if_fail (SYSPROF_IS_WINDOW (self)); - g_return_if_fail (G_IS_FILE (file)); + g_return_val_if_fail (SYSPROF_IS_APPLICATION (app), NULL); + g_return_val_if_fail (SYSPROF_IS_DOCUMENT (document), NULL); - sysprof_notebook_open (self->notebook, file); + return g_object_new (SYSPROF_TYPE_WINDOW, + "application", app, + "document", document, + NULL); +} + +/** + * sysprof_window_get_session: + * @self: a #SysprofWindow + * + * Gets the session for the window. + * + * Returns: (transfer none): a #SysprofSession + */ +SysprofSession * +sysprof_window_get_session (SysprofWindow *self) +{ + g_return_val_if_fail (SYSPROF_IS_WINDOW (self), NULL); + + return self->session; +} + +/** + * sysprof_window_get_document: + * @self: a #SysprofWindow + * + * Gets the document for the window. + * + * Returns: (transfer none): a #SysprofDocument + */ +SysprofDocument * +sysprof_window_get_document (SysprofWindow *self) +{ + g_return_val_if_fail (SYSPROF_IS_WINDOW (self), NULL); + + return self->document; } static void -sysprof_window_open_from_dialog_cb (SysprofWindow *self, - int response, - GtkFileChooserNative *dialog) +sysprof_window_load_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) { - g_assert (SYSPROF_IS_WINDOW (self)); - g_assert (GTK_IS_FILE_CHOOSER_NATIVE (dialog)); + SysprofDocumentLoader *loader = (SysprofDocumentLoader *)object; + g_autoptr(SysprofApplication) app = user_data; + g_autoptr(SysprofDocument) document = NULL; + g_autoptr(GError) error = NULL; - if (response == GTK_RESPONSE_ACCEPT) + g_assert (SYSPROF_IS_DOCUMENT_LOADER (loader)); + g_assert (G_IS_ASYNC_RESULT (result)); + g_assert (SYSPROF_IS_APPLICATION (app)); + + g_application_release (G_APPLICATION (app)); + + if (!(document = sysprof_document_loader_load_finish (loader, result, &error))) { - g_autoptr(GFile) file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + GtkWidget *dialog; - if (g_file_is_native (file)) - sysprof_window_open (self, file); + dialog = adw_message_dialog_new (NULL, _("Invalid Document"), NULL); + adw_message_dialog_format_body (ADW_MESSAGE_DIALOG (dialog), + _("The document could not be loaded. Please check that you have the correct capture file.\n\n%s"), + error->message); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), "close", _("Close")); + gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (dialog)); + gtk_window_present (GTK_WINDOW (dialog)); + } + else + { + GtkWidget *window; + + window = sysprof_window_new (app, document); + gtk_window_present (GTK_WINDOW (window)); + } +} + +static void +sysprof_window_apply_loader_settings (SysprofDocumentLoader *loader) +{ + /* TODO: apply loader settings from gsettings/etc */ +} + +void +sysprof_window_open (SysprofApplication *app, + GFile *file) +{ + g_autoptr(SysprofDocumentLoader) loader = NULL; + + g_return_if_fail (SYSPROF_IS_APPLICATION (app)); + g_return_if_fail (G_IS_FILE (file)); + + if (!g_file_is_native (file) || + !(loader = sysprof_document_loader_new (g_file_peek_path (file)))) + { + g_autofree char *uri = g_file_get_uri (file); + g_warning ("Cannot open non-native file \"%s\"", uri); + return; } - gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (dialog)); + g_application_hold (G_APPLICATION (app)); + sysprof_window_apply_loader_settings (loader); + sysprof_document_loader_load_async (loader, + NULL, + sysprof_window_load_cb, + g_object_ref (app)); + } void -sysprof_window_open_from_dialog (SysprofWindow *self) +sysprof_window_open_fd (SysprofApplication *app, + int fd) { - GtkFileChooserNative *dialog; - GtkFileFilter *filter; + g_autoptr(SysprofDocumentLoader) loader = NULL; + g_autoptr(GError) error = NULL; - g_return_if_fail (SYSPROF_IS_WINDOW (self)); + g_return_if_fail (SYSPROF_IS_APPLICATION (app)); - /* Translators: This is a window title. */ - dialog = gtk_file_chooser_native_new (_("Open Capture…"), - GTK_WINDOW (self), - GTK_FILE_CHOOSER_ACTION_OPEN, - /* Translators: This is a button. */ - _("Open"), - /* Translators: This is a button. */ - _("Cancel")); + if (!(loader = sysprof_document_loader_new_for_fd (fd, &error))) + { + g_critical ("Failed to dup FD: %s", error->message); + return; + } - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("Sysprof Captures")); - gtk_file_filter_add_pattern (filter, "*.syscap"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + g_debug ("Opening recording by FD"); - 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); + g_application_hold (G_APPLICATION (app)); + sysprof_window_apply_loader_settings (loader); + sysprof_document_loader_load_async (loader, + NULL, + sysprof_window_load_cb, + g_object_ref (app)); - g_signal_connect_object (dialog, - "response", - G_CALLBACK (sysprof_window_open_from_dialog_cb), - self, - G_CONNECT_SWAPPED); - - gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog)); -} - -void -sysprof_window_new_tab (SysprofWindow *self) -{ - GtkWidget *display; - gint page; - - g_return_if_fail (SYSPROF_IS_WINDOW (self)); - - display = sysprof_display_new (); - page = sysprof_notebook_append (self->notebook, SYSPROF_DISPLAY (display)); - sysprof_notebook_set_current_page (self->notebook, page); } diff --git a/src/sysprof/sysprof-window.h b/src/sysprof/sysprof-window.h index e0ca3ce5..649d3fc8 100644 --- a/src/sysprof/sysprof-window.h +++ b/src/sysprof/sysprof-window.h @@ -1,6 +1,6 @@ /* sysprof-window.h * - * Copyright 2016-2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,7 +22,10 @@ #include +#include + #include "sysprof-application.h" +#include "sysprof-session.h" G_BEGIN_DECLS @@ -30,10 +33,13 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (SysprofWindow, sysprof_window, SYSPROF, WINDOW, AdwApplicationWindow) -GtkWidget *sysprof_window_new (SysprofApplication *application); -void sysprof_window_new_tab (SysprofWindow *self); -void sysprof_window_open (SysprofWindow *self, - GFile *file); -void sysprof_window_open_from_dialog (SysprofWindow *self); +GtkWidget *sysprof_window_new (SysprofApplication *app, + SysprofDocument *document); +void sysprof_window_open (SysprofApplication *app, + GFile *file); +void sysprof_window_open_fd (SysprofApplication *app, + int fd); +SysprofDocument *sysprof_window_get_document (SysprofWindow *self); +SysprofSession *sysprof_window_get_session (SysprofWindow *self); G_END_DECLS diff --git a/src/sysprof/sysprof-window.ui b/src/sysprof/sysprof-window.ui index 046cd194..f34b1a79 100644 --- a/src/sysprof/sysprof-window.ui +++ b/src/sysprof/sysprof-window.ui @@ -1,59 +1,304 @@ - + + + + + +
+ + _Record Again… + win.record-capture + + + Open Recording… + win.open-capture + + + Save As… + +
+
+ + Preferences + app.preferences + <ctrl>comma + + + Help + F1 + app.help + + + About Sysprof + app.about + +
+
diff --git a/src/sysprof/sysprof-xy-layer-private.h b/src/sysprof/sysprof-xy-layer-private.h new file mode 100644 index 00000000..3de387cf --- /dev/null +++ b/src/sysprof/sysprof-xy-layer-private.h @@ -0,0 +1,54 @@ +/* sysprof-xy-layer-private.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-normalized-series.h" +#include "sysprof-xy-layer.h" + +G_BEGIN_DECLS + +struct _SysprofXYLayer +{ + SysprofChartLayer parent_instance; + + GBindingGroup *series_bindings; + SysprofXYSeries *series; + + SysprofAxis *x_axis; + SysprofAxis *y_axis; + + SysprofNormalizedSeries *normal_x; + SysprofNormalizedSeries *normal_y; + + guint flip_y : 1; +}; + +struct _SysprofXYLayerClass +{ + SysprofChartLayerClass parent_instance; +}; + +void _sysprof_xy_layer_get_xy (SysprofXYLayer *self, + const double **x_values, + const double **y_values, + guint *n_values); + +G_END_DECLS diff --git a/src/sysprof/sysprof-xy-layer.c b/src/sysprof/sysprof-xy-layer.c new file mode 100644 index 00000000..3ef5b4ac --- /dev/null +++ b/src/sysprof/sysprof-xy-layer.c @@ -0,0 +1,336 @@ +/* sysprof-xy-layer.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-axis-private.h" +#include "sysprof-xy-layer-private.h" + +enum { + PROP_0, + PROP_FLIP_Y, + PROP_NORMALIZED_X, + PROP_NORMALIZED_Y, + PROP_SERIES, + PROP_X_AXIS, + PROP_Y_AXIS, + N_PROPS +}; + +G_DEFINE_TYPE (SysprofXYLayer, sysprof_xy_layer, SYSPROF_TYPE_CHART_LAYER) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_xy_layer_dispose (GObject *object) +{ + SysprofXYLayer *self = (SysprofXYLayer *)object; + + g_clear_object (&self->series_bindings); + g_clear_object (&self->x_axis); + g_clear_object (&self->y_axis); + g_clear_object (&self->normal_x); + g_clear_object (&self->normal_y); + g_clear_object (&self->series); + + G_OBJECT_CLASS (sysprof_xy_layer_parent_class)->dispose (object); +} + +static void +sysprof_xy_layer_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofXYLayer *self = SYSPROF_XY_LAYER (object); + + switch (prop_id) + { + case PROP_SERIES: + g_value_set_object (value, sysprof_xy_layer_get_series (self)); + break; + + case PROP_X_AXIS: + g_value_set_object (value, sysprof_xy_layer_get_x_axis (self)); + break; + + case PROP_Y_AXIS: + g_value_set_object (value, sysprof_xy_layer_get_y_axis (self)); + break; + + case PROP_NORMALIZED_X: + /* For inspector debugging */ + g_value_set_object (value, self->normal_x); + break; + + case PROP_NORMALIZED_Y: + /* For inspector debugging */ + g_value_set_object (value, self->normal_y); + break; + + case PROP_FLIP_Y: + g_value_set_boolean (value, sysprof_xy_layer_get_flip_y (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_xy_layer_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofXYLayer *self = SYSPROF_XY_LAYER (object); + + switch (prop_id) + { + case PROP_SERIES: + sysprof_xy_layer_set_series (self, g_value_get_object (value)); + break; + + case PROP_X_AXIS: + sysprof_xy_layer_set_x_axis (self, g_value_get_object (value)); + break; + + case PROP_Y_AXIS: + sysprof_xy_layer_set_y_axis (self, g_value_get_object (value)); + break; + + case PROP_FLIP_Y: + sysprof_xy_layer_set_flip_y (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_xy_layer_class_init (SysprofXYLayerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = sysprof_xy_layer_dispose; + object_class->get_property = sysprof_xy_layer_get_property; + object_class->set_property = sysprof_xy_layer_set_property; + + properties[PROP_SERIES] = + g_param_spec_object ("series", NULL, NULL, + SYSPROF_TYPE_XY_SERIES, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_X_AXIS] = + g_param_spec_object ("x-axis", NULL, NULL, + SYSPROF_TYPE_AXIS, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties[PROP_Y_AXIS] = + g_param_spec_object ("y-axis", NULL, NULL, + SYSPROF_TYPE_AXIS, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_NORMALIZED_X] = + g_param_spec_object ("normalized-x", NULL, NULL, + SYSPROF_TYPE_NORMALIZED_SERIES, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_NORMALIZED_Y] = + g_param_spec_object ("normalized-y", NULL, NULL, + SYSPROF_TYPE_NORMALIZED_SERIES, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + properties [PROP_FLIP_Y] = + g_param_spec_boolean ("flip-y", NULL, NULL, + FALSE, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_xy_layer_init (SysprofXYLayer *self) +{ + self->normal_x = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES, NULL); + g_signal_connect_object (self->normal_x, + "items-changed", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); + + self->normal_y = g_object_new (SYSPROF_TYPE_NORMALIZED_SERIES, NULL); + g_signal_connect_object (self->normal_y, + "items-changed", + G_CALLBACK (gtk_widget_queue_draw), + self, + G_CONNECT_SWAPPED); + + self->series_bindings = g_binding_group_new (); + g_binding_group_bind (self->series_bindings, "x-expression", + self->normal_x, "expression", + G_BINDING_SYNC_CREATE); + g_binding_group_bind (self->series_bindings, "y-expression", + self->normal_y, "expression", + G_BINDING_SYNC_CREATE); +} + +void +_sysprof_xy_layer_get_xy (SysprofXYLayer *self, + const double **x_values, + const double **y_values, + guint *n_values) +{ + guint n_x_values = 0; + guint n_y_values = 0; + + g_return_if_fail (SYSPROF_IS_XY_LAYER (self)); + g_return_if_fail (x_values != NULL); + g_return_if_fail (y_values != NULL); + g_return_if_fail (n_values != NULL); + + if (self->x_axis == NULL || _sysprof_axis_is_pathological (self->x_axis) || + self->y_axis == NULL || _sysprof_axis_is_pathological (self->y_axis)) + { + *n_values = 0; + *x_values = NULL; + *y_values = NULL; + + return; + } + + *x_values = sysprof_normalized_series_get_values (self->normal_x, &n_x_values); + *y_values = sysprof_normalized_series_get_values (self->normal_y, &n_y_values); + *n_values = MIN (n_x_values, n_y_values); +} + +/** + * sysprof_xy_layer_get_series: + * @self: a #SysprofXYLayer + * + * Returns: (transfer none) (nullable): a #SysprofXYSeries or %NULL + */ +SysprofXYSeries * +sysprof_xy_layer_get_series (SysprofXYLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_LAYER (self), NULL); + + return self->series; +} + +void +sysprof_xy_layer_set_series (SysprofXYLayer *self, + SysprofXYSeries *series) +{ + g_return_if_fail (SYSPROF_IS_XY_LAYER (self)); + g_return_if_fail (!series || SYSPROF_IS_XY_SERIES (series)); + + if (!g_set_object (&self->series, series)) + return; + + g_binding_group_set_source (self->series_bindings, series); + + sysprof_normalized_series_set_series (self->normal_x, SYSPROF_SERIES (series)); + sysprof_normalized_series_set_series (self->normal_y, SYSPROF_SERIES (series)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SERIES]); +} + +/** + * sysprof_xy_layer_get_x_axis: + * @self: a #SysprofXYLayer + * + * Returns: (transfer none) (nullable): a #SysprofAxis or %NULL + */ +SysprofAxis * +sysprof_xy_layer_get_x_axis (SysprofXYLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_LAYER (self), NULL); + + return self->x_axis; +} + +void +sysprof_xy_layer_set_x_axis (SysprofXYLayer *self, + SysprofAxis *x_axis) +{ + g_return_if_fail (SYSPROF_IS_XY_LAYER (self)); + g_return_if_fail (!x_axis || SYSPROF_IS_AXIS (x_axis)); + + if (!g_set_object (&self->x_axis, x_axis)) + return; + + sysprof_normalized_series_set_axis (self->normal_x, x_axis); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_X_AXIS]); +} + +/** + * sysprof_xy_layer_get_y_axis: + * @self: a #SysprofXYLayer + * + * Returns: (transfer none) (nullable): a #SysprofAxis or %NULL + */ +SysprofAxis * +sysprof_xy_layer_get_y_axis (SysprofXYLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_LAYER (self), NULL); + + return self->y_axis; +} + +void +sysprof_xy_layer_set_y_axis (SysprofXYLayer *self, + SysprofAxis *y_axis) +{ + g_return_if_fail (SYSPROF_IS_XY_LAYER (self)); + g_return_if_fail (!y_axis || SYSPROF_IS_AXIS (y_axis)); + + if (!g_set_object (&self->y_axis, y_axis)) + return; + + sysprof_normalized_series_set_axis (self->normal_y, y_axis); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_Y_AXIS]); +} + +gboolean +sysprof_xy_layer_get_flip_y (SysprofXYLayer *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_LAYER (self), FALSE); + + return self->flip_y; +} + +void +sysprof_xy_layer_set_flip_y (SysprofXYLayer *self, + gboolean flip_y) +{ + g_return_if_fail (SYSPROF_IS_XY_LAYER (self)); + + flip_y = !!flip_y; + + if (flip_y != self->flip_y) + { + self->flip_y = flip_y; + sysprof_normalized_series_set_inverted (self->normal_y, flip_y); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FLIP_Y]); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} diff --git a/src/sysprof/sysprof-xy-layer.h b/src/sysprof/sysprof-xy-layer.h new file mode 100644 index 00000000..ab069bb5 --- /dev/null +++ b/src/sysprof/sysprof-xy-layer.h @@ -0,0 +1,49 @@ +/* sysprof-xy-layer.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-axis.h" +#include "sysprof-chart-layer.h" +#include "sysprof-xy-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_XY_LAYER (sysprof_xy_layer_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofXYLayer, sysprof_xy_layer, SYSPROF, XY_LAYER, SysprofChartLayer) + +SysprofChartLayer *sysprof_xy_layer_new (void); +gboolean sysprof_xy_layer_get_flip_y (SysprofXYLayer *self); +void sysprof_xy_layer_set_flip_y (SysprofXYLayer *self, + gboolean flip_y); +SysprofXYSeries *sysprof_xy_layer_get_series (SysprofXYLayer *self); +void sysprof_xy_layer_set_series (SysprofXYLayer *self, + SysprofXYSeries *series); +SysprofAxis *sysprof_xy_layer_get_x_axis (SysprofXYLayer *self); +void sysprof_xy_layer_set_x_axis (SysprofXYLayer *self, + SysprofAxis *x_axis); +SysprofAxis *sysprof_xy_layer_get_y_axis (SysprofXYLayer *self); +void sysprof_xy_layer_set_y_axis (SysprofXYLayer *self, + SysprofAxis *y_axis); + +G_END_DECLS diff --git a/src/libsysprof-ui/sysprof-time-label.h b/src/sysprof/sysprof-xy-series-item-private.h similarity index 67% rename from src/libsysprof-ui/sysprof-time-label.h rename to src/sysprof/sysprof-xy-series-item-private.h index 7dfc5707..be99977c 100644 --- a/src/libsysprof-ui/sysprof-time-label.h +++ b/src/sysprof/sysprof-xy-series-item-private.h @@ -1,6 +1,6 @@ -/* sysprof-time-label.h +/* sysprof-xy-series-item-private.h * - * Copyright 2019 Christian Hergert + * Copyright 2023 Christian Hergert * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,13 +22,12 @@ #include +#include "sysprof-xy-series-item.h" + G_BEGIN_DECLS -#define SYSPROF_TYPE_TIME_LABEL (sysprof_time_label_get_type()) - -G_DECLARE_FINAL_TYPE (SysprofTimeLabel, sysprof_time_label, SYSPROF, TIME_LABEL, GtkWidget) - -void sysprof_time_label_set_duration (SysprofTimeLabel *self, - guint duration); +SysprofXYSeriesItem *_sysprof_xy_series_item_new (GObject *item, + GtkExpression *x_expression, + GtkExpression *y_expression); G_END_DECLS diff --git a/src/sysprof/sysprof-xy-series-item.c b/src/sysprof/sysprof-xy-series-item.c new file mode 100644 index 00000000..87b4c5d2 --- /dev/null +++ b/src/sysprof/sysprof-xy-series-item.c @@ -0,0 +1,142 @@ +/* sysprof-xy-series-item.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-xy-series-item-private.h" + +struct _SysprofXYSeriesItem +{ + GObject parent_instance; + GtkExpression *x_expression; + GtkExpression *y_expression; + GObject *item; +}; + +enum { + PROP_0, + PROP_ITEM, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofXYSeriesItem, sysprof_xy_series_item, G_TYPE_OBJECT) + +static GParamSpec *properties [N_PROPS]; + +static void +sysprof_xy_series_item_finalize (GObject *object) +{ + SysprofXYSeriesItem *self = (SysprofXYSeriesItem *)object; + + g_clear_object (&self->item); + g_clear_pointer (&self->x_expression, gtk_expression_unref); + g_clear_pointer (&self->y_expression, gtk_expression_unref); + + G_OBJECT_CLASS (sysprof_xy_series_item_parent_class)->finalize (object); +} + +static void +sysprof_xy_series_item_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofXYSeriesItem *self = SYSPROF_XY_SERIES_ITEM (object); + + switch (prop_id) + { + case PROP_ITEM: + g_value_set_object (value, self->item); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_xy_series_item_class_init (SysprofXYSeriesItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = sysprof_xy_series_item_finalize; + object_class->get_property = sysprof_xy_series_item_get_property; + + properties [PROP_ITEM] = + g_param_spec_object ("item", NULL, NULL, + G_TYPE_OBJECT, + (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_xy_series_item_init (SysprofXYSeriesItem *self) +{ +} + +SysprofXYSeriesItem * +_sysprof_xy_series_item_new (GObject *item, + GtkExpression *x_expression, + GtkExpression *y_expression) +{ + SysprofXYSeriesItem *self; + + self = g_object_new (SYSPROF_TYPE_XY_SERIES_ITEM, NULL); + self->item = item; + self->x_expression = x_expression; + self->y_expression = y_expression; + + return self; +} + +/** + * sysprof_xy_series_item_get_item: + * @self: a #SysprofXYSeriesItem + * + * Gets the item used to generate X/Y values. + * + * Returns: (transfer none): a #GObject + */ +gpointer +sysprof_xy_series_item_get_item (SysprofXYSeriesItem *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_SERIES_ITEM (self), NULL); + + return self->item; +} + +void +sysprof_xy_series_item_get_x_value (SysprofXYSeriesItem *self, + GValue *value) +{ + g_return_if_fail (SYSPROF_IS_XY_SERIES_ITEM (self)); + + gtk_expression_evaluate (self->x_expression, self->item, value); +} + +void +sysprof_xy_series_item_get_y_value (SysprofXYSeriesItem *self, + GValue *value) +{ + g_return_if_fail (SYSPROF_IS_XY_SERIES_ITEM (self)); + + gtk_expression_evaluate (self->y_expression, self->item, value); +} diff --git a/src/sysprof/sysprof-xy-series-item.h b/src/sysprof/sysprof-xy-series-item.h new file mode 100644 index 00000000..368dd920 --- /dev/null +++ b/src/sysprof/sysprof-xy-series-item.h @@ -0,0 +1,39 @@ +/* sysprof-xy-series-item.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_XY_SERIES_ITEM (sysprof_xy_series_item_get_type()) + +G_DECLARE_FINAL_TYPE (SysprofXYSeriesItem, sysprof_xy_series_item, SYSPROF, XY_SERIES_ITEM, GObject) + +void sysprof_xy_series_item_get_x_value (SysprofXYSeriesItem *self, + GValue *x_value); +void sysprof_xy_series_item_get_y_value (SysprofXYSeriesItem *self, + GValue *y_value); +gpointer sysprof_xy_series_item_get_item (SysprofXYSeriesItem *self); + +G_END_DECLS diff --git a/src/sysprof/sysprof-xy-series.c b/src/sysprof/sysprof-xy-series.c new file mode 100644 index 00000000..b94d06c3 --- /dev/null +++ b/src/sysprof/sysprof-xy-series.c @@ -0,0 +1,255 @@ +/* sysprof-xy-series.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include "sysprof-series-private.h" +#include "sysprof-xy-series.h" +#include "sysprof-xy-series-item-private.h" + +struct _SysprofXYSeries +{ + SysprofSeries parent_instance; + GtkExpression *x_expression; + GtkExpression *y_expression; +}; + +struct _SysprofXYSeriesClass +{ + SysprofSeriesClass parent_instance; +}; + +enum { + PROP_0, + PROP_X_EXPRESSION, + PROP_Y_EXPRESSION, + N_PROPS +}; + +G_DEFINE_FINAL_TYPE (SysprofXYSeries, sysprof_xy_series, SYSPROF_TYPE_SERIES) + +static GParamSpec *properties [N_PROPS]; + +static gpointer +sysprof_xy_series_get_series_item (SysprofSeries *series, + guint position, + gpointer item) +{ + SysprofXYSeries *self = SYSPROF_XY_SERIES (series); + + return _sysprof_xy_series_item_new (item, + gtk_expression_ref (self->x_expression), + gtk_expression_ref (self->y_expression)); +} + +static void +sysprof_xy_series_finalize (GObject *object) +{ + SysprofXYSeries *self = (SysprofXYSeries *)object; + + g_clear_pointer (&self->x_expression, gtk_expression_unref); + g_clear_pointer (&self->y_expression, gtk_expression_unref); + + G_OBJECT_CLASS (sysprof_xy_series_parent_class)->finalize (object); +} + +static void +sysprof_xy_series_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + SysprofXYSeries *self = SYSPROF_XY_SERIES (object); + + switch (prop_id) + { + case PROP_X_EXPRESSION: + gtk_value_set_expression (value, self->x_expression); + break; + + case PROP_Y_EXPRESSION: + gtk_value_set_expression (value, self->y_expression); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_xy_series_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + SysprofXYSeries *self = SYSPROF_XY_SERIES (object); + + switch (prop_id) + { + case PROP_X_EXPRESSION: + sysprof_xy_series_set_x_expression (self, gtk_value_get_expression (value)); + break; + + case PROP_Y_EXPRESSION: + sysprof_xy_series_set_y_expression (self, gtk_value_get_expression (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +sysprof_xy_series_class_init (SysprofXYSeriesClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + SysprofSeriesClass *series_class = SYSPROF_SERIES_CLASS (klass); + + object_class->finalize = sysprof_xy_series_finalize; + object_class->get_property = sysprof_xy_series_get_property; + object_class->set_property = sysprof_xy_series_set_property; + + series_class->series_item_type = SYSPROF_TYPE_XY_SERIES_ITEM; + series_class->get_series_item = sysprof_xy_series_get_series_item; + + properties [PROP_X_EXPRESSION] = + gtk_param_spec_expression ("x-expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_Y_EXPRESSION] = + gtk_param_spec_expression ("y-expression", NULL, NULL, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +sysprof_xy_series_init (SysprofXYSeries *self) +{ +} + +/** + * sysprof_xy_series_new: + * @title: (nullable): a title for the series + * @model: (transfer full) (nullable): a #GListModel for the series + * @x_expression: (transfer full) (nullable): a #GtkExpression for + * extracting the X value from @model items. + * @y_expression: (transfer full) (nullable): a #GtkExpression for + * extracting the Y value from @model items. + * + * A #SysprofSeries which contains X,Y value pairs. + * + * Returns: (transfer full): a #SysprofSeries + */ +SysprofSeries * +sysprof_xy_series_new (const char *title, + GListModel *model, + GtkExpression *x_expression, + GtkExpression *y_expression) +{ + SysprofXYSeries *xy; + + xy = g_object_new (SYSPROF_TYPE_XY_SERIES, + "title", title, + "model", model, + "x-expression", x_expression, + "y-expression", y_expression, + NULL); + + g_clear_pointer (&x_expression, gtk_expression_unref); + g_clear_pointer (&y_expression, gtk_expression_unref); + g_clear_object (&model); + + return SYSPROF_SERIES (xy); +} + +/** + * sysprof_xy_series_get_x_expression: + * @self: a #SysprofXYSeries + * + * Gets the #SysprofXYSeries:x-expression property. + * + * This is used to extract the X coordinate from items in the #GListModel. + * + * Returns: (transfer none) (nullable): a #GtkExpression or %NULL + */ +GtkExpression * +sysprof_xy_series_get_x_expression (SysprofXYSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_SERIES (self), NULL); + + return self->x_expression; +} + +void +sysprof_xy_series_set_x_expression (SysprofXYSeries *self, + GtkExpression *x_expression) +{ + g_return_if_fail (SYSPROF_IS_XY_SERIES (self)); + + if (self->x_expression == x_expression) + return; + + if (x_expression) + gtk_expression_ref (x_expression); + + g_clear_pointer (&self->x_expression, gtk_expression_unref); + + self->x_expression = x_expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_X_EXPRESSION]); +} + +/** + * sysprof_xy_series_get_y_expression: + * @self: a #SysprofXYSeries + * + * Gets the #SysprofXYSeries:y-expression property. + * + * This is used to extract the Y coordinate from items in the #GListModel. + * + * Returns: (transfer none) (nullable): a #GtkExpression or %NULL + */ +GtkExpression * +sysprof_xy_series_get_y_expression (SysprofXYSeries *self) +{ + g_return_val_if_fail (SYSPROF_IS_XY_SERIES (self), NULL); + + return self->y_expression; +} + +void +sysprof_xy_series_set_y_expression (SysprofXYSeries *self, + GtkExpression *y_expression) +{ + g_return_if_fail (SYSPROF_IS_XY_SERIES (self)); + + if (self->y_expression == y_expression) + return; + + if (y_expression) + gtk_expression_ref (y_expression); + + g_clear_pointer (&self->y_expression, gtk_expression_unref); + + self->y_expression = y_expression; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_X_EXPRESSION]); +} diff --git a/src/sysprof/sysprof-xy-series.h b/src/sysprof/sysprof-xy-series.h new file mode 100644 index 00000000..5efcf41e --- /dev/null +++ b/src/sysprof/sysprof-xy-series.h @@ -0,0 +1,51 @@ +/* sysprof-xy-series.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include "sysprof-series.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_XY_SERIES (sysprof_xy_series_get_type()) +#define SYSPROF_IS_XY_SERIES(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_XY_SERIES)) +#define SYSPROF_XY_SERIES(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_XY_SERIES, SysprofXYSeries)) +#define SYSPROF_XY_SERIES_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_XY_SERIES, SysprofXYSeriesClass)) + +typedef struct _SysprofXYSeries SysprofXYSeries; +typedef struct _SysprofXYSeriesClass SysprofXYSeriesClass; + +GType sysprof_xy_series_get_type (void) G_GNUC_CONST; +SysprofSeries *sysprof_xy_series_new (const char *title, + GListModel *model, + GtkExpression *x_expression, + GtkExpression *y_expression); +GtkExpression *sysprof_xy_series_get_x_expression (SysprofXYSeries *self); +void sysprof_xy_series_set_x_expression (SysprofXYSeries *self, + GtkExpression *x_expression); +GtkExpression *sysprof_xy_series_get_y_expression (SysprofXYSeries *self); +void sysprof_xy_series_set_y_expression (SysprofXYSeries *self, + GtkExpression *y_expression); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofXYSeries, g_object_unref) + +G_END_DECLS diff --git a/src/sysprof/sysprof.gresource.xml b/src/sysprof/sysprof.gresource.xml index faee1ac4..c7ba1cdd 100644 --- a/src/sysprof/sysprof.gresource.xml +++ b/src/sysprof/sysprof.gresource.xml @@ -1,15 +1,43 @@ - gtk/help-overlay.ui gtk/menus.ui - - - theme/shared.css - - - + icons/scalable/actions/address-layout-symbolic.svg + icons/scalable/actions/empty-symbolic.svg + icons/scalable/actions/mark-chart-symbolic.svg + icons/scalable/actions/mark-table-symbolic.svg + icons/scalable/actions/mark-waterfall-symbolic.svg + icons/scalable/actions/memory-allocations-symbolic.svg + icons/scalable/actions/metadata-symbolic.svg + icons/scalable/actions/process-mounts-symbolic.svg + icons/scalable/actions/storage-symbolic.svg + icons/scalable/actions/system-log-symbolic.svg + icons/scalable/actions/threads-symbolic.svg + sysprof-callgraph-view.ui + sysprof-counters-section.ui + sysprof-cpu-section.ui + sysprof-cpu-section-counter.ui + sysprof-files-section.ui + sysprof-frame-utility.ui + sysprof-greeter.ui + sysprof-logs-section.ui + sysprof-mark-chart-row.ui + sysprof-mark-chart.ui + sysprof-mark-table.ui + sysprof-marks-section.ui + sysprof-memory-callgraph-view.ui + sysprof-memory-section.ui + sysprof-metadata-section.ui + sysprof-process-dialog.ui + sysprof-processes-section.ui + sysprof-recording-pad.ui + sysprof-samples-section.ui + sysprof-sidebar.ui + sysprof-time-scrubber.ui + sysprof-traceables-utility.ui + sysprof-weighted-callgraph-view.ui sysprof-window.ui + style.css diff --git a/src/sysprof/theme/shared.css b/src/sysprof/theme/shared.css deleted file mode 100644 index 878dcb69..00000000 --- a/src/sysprof/theme/shared.css +++ /dev/null @@ -1,12 +0,0 @@ -visualizers list { - background: @theme_bg_color; -} - -visualizers list row:backdrop, -visualizers list row:last-child { - border-bottom: none; -} - -ticks { - color: alpha(@theme_fg_color, 0.4); -} diff --git a/src/helpers.c b/src/sysprofd/helpers.c similarity index 100% rename from src/helpers.c rename to src/sysprofd/helpers.c diff --git a/src/helpers.h b/src/sysprofd/helpers.h similarity index 100% rename from src/helpers.h rename to src/sysprofd/helpers.h diff --git a/src/sysprofd/ipc-legacy-impl.c b/src/sysprofd/ipc-legacy-impl.c deleted file mode 100644 index 445b7636..00000000 --- a/src/sysprofd/ipc-legacy-impl.c +++ /dev/null @@ -1,218 +0,0 @@ -/* ipc-legacy-impl.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "ipc-legacy-impl" - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../libsysprof/sysprof-kallsyms.h" -#include "helpers.h" -#include "ipc-legacy-impl.h" - -struct _IpcLegacyImpl -{ - IpcLegacySysprof2Skeleton parent; -}; - -enum { - ACTIVITY, - N_SIGNALS -}; - -static guint signals [N_SIGNALS]; - -static gboolean -ipc_legacy_impl_handle_perf_event_open (IpcLegacySysprof2 *service, - GDBusMethodInvocation *invocation, - GUnixFDList *fd_list, - GVariant *options, - gint32 pid, - gint32 cpu, - guint64 flags) -{ - gint out_fd = -1; - gint handle; - - g_assert (IPC_IS_LEGACY_IMPL (service)); - g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); - - g_message ("LEGACY: PerfEventOpen(pid=%d, cpu=%d)", pid, cpu); - - errno = 0; - if (!helpers_perf_event_open (options, pid, cpu, -1, flags, &out_fd)) - { - g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), - G_DBUS_ERROR, - G_DBUS_ERROR_FAILED, - "Failed to create perf counter: %s", - g_strerror (errno)); - } - else - { - g_autoptr(GUnixFDList) out_fd_list = g_unix_fd_list_new (); - g_autoptr(GError) error = NULL; - - if (-1 == (handle = g_unix_fd_list_append (out_fd_list, out_fd, &error))) - { - g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), - G_DBUS_ERROR, - G_DBUS_ERROR_LIMITS_EXCEEDED, - "Failed to create file-descriptor for reply"); - } - else - { - ipc_legacy_sysprof2_complete_perf_event_open (service, - g_steal_pointer (&invocation), - out_fd_list, - g_variant_new ("h", handle)); - } - } - - if (out_fd != -1) - close (out_fd); - - return TRUE; -} - -static gboolean -ipc_legacy_impl_handle_get_kernal_symbols (IpcLegacySysprof2 *service, - GDBusMethodInvocation *invocation) -{ - g_autoptr(SysprofKallsyms) kallsyms = NULL; - GVariantBuilder builder; - const gchar *name; - guint64 addr; - guint8 type; - - g_assert (IPC_IS_LEGACY_IMPL (service)); - g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); - - g_message ("LEGACY: GetKernelSymbols()"); - - if (!(kallsyms = sysprof_kallsyms_new ("/proc/kallsyms"))) - { - g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), - G_DBUS_ERROR, - G_DBUS_ERROR_FAILED, - "Failed to create parse kallsyms"); - return TRUE; - } - - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(tys)")); - while (sysprof_kallsyms_next (kallsyms, &name, &addr, &type)) - g_variant_builder_add (&builder, "(tys)", addr, type, name); - - ipc_legacy_sysprof2_complete_get_kernel_symbols (service, - g_steal_pointer (&invocation), - g_variant_builder_end (&builder)); - - return TRUE; -} - -static gboolean -ipc_legacy_impl_g_authorize_method (GDBusInterfaceSkeleton *skeleton, - GDBusMethodInvocation *invocation) -{ - PolkitAuthorizationResult *res = NULL; - PolkitAuthority *authority = NULL; - PolkitSubject *subject = NULL; - const gchar *peer_name; - gboolean ret = TRUE; - - g_assert (IPC_IS_LEGACY_IMPL (skeleton)); - g_assert (G_IS_DBUS_METHOD_INVOCATION (invocation)); - - g_signal_emit (skeleton, signals [ACTIVITY], 0); - - peer_name = g_dbus_method_invocation_get_sender (invocation); - - if (!(authority = polkit_authority_get_sync (NULL, NULL)) || - !(subject = polkit_system_bus_name_new (peer_name)) || - !(res = polkit_authority_check_authorization_sync (authority, - POLKIT_SUBJECT (subject), - "org.gnome.sysprof3.profile", - NULL, - POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, - NULL, - NULL)) || - !polkit_authorization_result_get_is_authorized (res)) - { - g_dbus_method_invocation_return_error (g_steal_pointer (&invocation), - G_DBUS_ERROR, - G_DBUS_ERROR_ACCESS_DENIED, - "Not authorized to make request"); - ret = FALSE; - } - - g_clear_object (&authority); - g_clear_object (&subject); - g_clear_object (&res); - - return ret; -} - -static void -sysprof2_iface_init (IpcLegacySysprof2Iface *iface) -{ - iface->handle_perf_event_open = ipc_legacy_impl_handle_perf_event_open; - iface->handle_get_kernel_symbols = ipc_legacy_impl_handle_get_kernal_symbols; -} - -G_DEFINE_TYPE_WITH_CODE (IpcLegacyImpl, ipc_legacy_impl, IPC_LEGACY_TYPE_SYSPROF2_SKELETON, - G_IMPLEMENT_INTERFACE (IPC_LEGACY_TYPE_SYSPROF2, sysprof2_iface_init)) - -static void -ipc_legacy_impl_class_init (IpcLegacyImplClass *klass) -{ - GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass); - - skeleton_class->g_authorize_method = ipc_legacy_impl_g_authorize_method; - - signals [ACTIVITY] = - g_signal_new ("activity", - G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - NULL, - G_TYPE_NONE, 0); -} - -static void -ipc_legacy_impl_init (IpcLegacyImpl *self) -{ - g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (self), - G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); -} - -IpcLegacySysprof2 * -ipc_legacy_impl_new (void) -{ - return g_object_new (IPC_TYPE_LEGACY_IMPL, NULL); -} diff --git a/src/sysprofd/ipc-legacy-impl.h b/src/sysprofd/ipc-legacy-impl.h deleted file mode 100644 index e0c2c673..00000000 --- a/src/sysprofd/ipc-legacy-impl.h +++ /dev/null @@ -1,33 +0,0 @@ -/* ipc-legacy-impl.h - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#pragma once - -#include "ipc-legacy.h" - -G_BEGIN_DECLS - -#define IPC_TYPE_LEGACY_IMPL (ipc_legacy_impl_get_type()) - -G_DECLARE_FINAL_TYPE (IpcLegacyImpl, ipc_legacy_impl, IPC, LEGACY_IMPL, IpcLegacySysprof2Skeleton) - -IpcLegacySysprof2 *ipc_legacy_impl_new (void); - -G_END_DECLS diff --git a/src/sysprofd/meson.build b/src/sysprofd/meson.build index b6a8dd97..456d7a9a 100644 --- a/src/sysprofd/meson.build +++ b/src/sysprofd/meson.build @@ -1,12 +1,21 @@ +ipc_profiler_src = gnome.gdbus_codegen('ipc-profiler', + sources: 'org.gnome.Sysprof3.Profiler.xml', + interface_prefix: 'org.gnome.Sysprof3.', + namespace: 'Ipc', +) + +ipc_service_src = gnome.gdbus_codegen('ipc-service', + sources: 'org.gnome.Sysprof3.Service.xml', + interface_prefix: 'org.gnome.Sysprof3.', + namespace: 'Ipc', +) + sysprofd_sources = [ - '../libsysprof/sysprof-kallsyms.c', 'sysprofd.c', - 'ipc-legacy-impl.c', 'ipc-rapl-profiler.c', 'ipc-service-impl.c', 'sysprof-turbostat.c', - helpers_sources, - ipc_legacy_src, + 'helpers.c', ipc_profiler_src, ipc_service_src, ] @@ -17,7 +26,8 @@ sysprofd_deps = [ dependency('glib-2.0', version: glib_req_version), dependency('gio-2.0', version: glib_req_version), dependency('gio-unix-2.0', version: glib_req_version), - dependency('polkit-gobject-1', version: polkit_req_version), + polkit_dep, + libsysprof_capture_dep, ] @@ -26,7 +36,7 @@ sysprofd = executable('sysprofd', sysprofd_sources, install: true, install_dir: pkglibexecdir, pie: true, - include_directories: [include_directories('.'), ipc_include_dirs], + include_directories: [include_directories('.')], ) sysprofdconf = configuration_data() @@ -70,27 +80,9 @@ i18n.merge_file( install_dir: join_paths(datadir, 'polkit-1/actions'), ) -# -# For org.gnome.Sysprof2 Compatibility -# - -configure_file( - input: 'org.gnome.Sysprof2.service.in', - output: 'org.gnome.Sysprof2.service', - configuration: sysprofdconf, - install_dir: join_paths(datadir, 'dbus-1/system-services'), -) - -configure_file( - input: 'org.gnome.Sysprof2.conf.in', - output: 'org.gnome.Sysprof2.conf', - configuration: sysprofdconf, - install_dir: join_paths(datadir, 'dbus-1/system.d'), -) - -configure_file( - input: 'sysprof2.service.in', - output: 'sysprof2.service', - configuration: sysprofdconf, - install_dir: systemdunitdir, +install_data([ + 'org.gnome.Sysprof3.Profiler.xml', + 'org.gnome.Sysprof3.Service.xml', + ], + install_dir: join_paths(datadir, 'dbus-1/interfaces'), ) diff --git a/src/sysprofd/org.gnome.Sysprof2.conf.in b/src/sysprofd/org.gnome.Sysprof2.conf.in deleted file mode 100644 index c46c7a6b..00000000 --- a/src/sysprofd/org.gnome.Sysprof2.conf.in +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/src/sysprofd/org.gnome.Sysprof2.service.in b/src/sysprofd/org.gnome.Sysprof2.service.in deleted file mode 100644 index 3455b650..00000000 --- a/src/sysprofd/org.gnome.Sysprof2.service.in +++ /dev/null @@ -1,5 +0,0 @@ -[D-BUS Service] -Name=org.gnome.Sysprof2 -Exec=@sysprofdprivdir@/sysprofd -User=root -SystemdService=sysprof2.service diff --git a/src/org.gnome.Sysprof3.Profiler.xml b/src/sysprofd/org.gnome.Sysprof3.Profiler.xml similarity index 100% rename from src/org.gnome.Sysprof3.Profiler.xml rename to src/sysprofd/org.gnome.Sysprof3.Profiler.xml diff --git a/src/org.gnome.Sysprof3.Service.xml b/src/sysprofd/org.gnome.Sysprof3.Service.xml similarity index 100% rename from src/org.gnome.Sysprof3.Service.xml rename to src/sysprofd/org.gnome.Sysprof3.Service.xml diff --git a/src/sysprofd/sysprof2.service.in b/src/sysprofd/sysprof2.service.in deleted file mode 100644 index 9ee46328..00000000 --- a/src/sysprofd/sysprof2.service.in +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Sysprof Daemon - -[Service] -Type=dbus -BusName=org.gnome.Sysprof2 -ExecStart=@sysprofdprivdir@/sysprofd - diff --git a/src/sysprofd/sysprofd.c b/src/sysprofd/sysprofd.c index 12fc1c24..41bc35e1 100644 --- a/src/sysprofd/sysprofd.c +++ b/src/sysprofd/sysprofd.c @@ -26,10 +26,8 @@ #include #include -#include "ipc-legacy.h" #include "ipc-service.h" -#include "ipc-legacy-impl.h" #include "ipc-rapl-profiler.h" #include "ipc-service-impl.h" @@ -127,18 +125,15 @@ main (gint argc, if ((bus = g_bus_get_sync (bus_type, NULL, &error))) { - g_autoptr(IpcLegacySysprof2) v2_service = ipc_legacy_impl_new (); g_autoptr(IpcProfiler) rapl = ipc_rapl_profiler_new (); g_autoptr(IpcService) v3_service = ipc_service_impl_new (); g_signal_connect (v3_service, "activity", G_CALLBACK (activity_cb), NULL); - g_signal_connect (v2_service, "activity", G_CALLBACK (activity_cb), NULL); g_signal_connect (rapl, "activity", G_CALLBACK (activity_cb), NULL); activity_cb (NULL, NULL); if (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v3_service), bus, V3_PATH, &error) && - g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (v2_service), bus, V2_PATH, &error) && g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (rapl), bus, RAPL_PATH, &error)) { for (guint i = 0; i < G_N_ELEMENTS (bus_names); i++) diff --git a/src/tests/allocs-within-mark.c b/src/tests/allocs-within-mark.c deleted file mode 100644 index e149ab45..00000000 --- a/src/tests/allocs-within-mark.c +++ /dev/null @@ -1,220 +0,0 @@ -/* allocs-within-mark.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include -#include -#include -#include - -typedef struct -{ - gint64 begin; - gint64 end; - gint64 allocated; -} Interval; - -static gint -compare_interval (gconstpointer a, - gconstpointer b) -{ - const Interval *aptr = a; - const Interval *bptr = b; - - if (aptr->begin < bptr->begin) - return -1; - else if (aptr->begin > bptr->begin) - return 1; - - if (aptr->end < bptr->end) - return -1; - else if (aptr->end > bptr->end) - return 1; - - return 0; -} - -static gint -find_time_cb (gconstpointer key, - gconstpointer b) -{ - const Interval *keyptr = key; - const Interval *bptr = b; - - if (keyptr->begin >= bptr->begin && keyptr->end <= bptr->end) - return 0; - - return compare_interval (key, b); -} - -static inline Interval * -find_time (GArray *ar, - gint64 time) -{ - Interval key = {time, time, 0}; - - return bsearch (&key, ar->data, ar->len, sizeof (Interval), find_time_cb); -} - -static void -allocs_within_mark (SysprofCaptureReader *reader, - const gchar *category, - const gchar *name) -{ - g_autoptr(GArray) intervals = g_array_new (FALSE, FALSE, sizeof (Interval)); - SysprofCaptureFrameType type; - gint64 begin; - struct { - gint64 total; - gint64 min; - gint64 max; - } st = { 0, G_MAXINT64, G_MININT64 }; - - g_assert (reader); - g_assert (category); - g_assert (name); - - begin = sysprof_capture_reader_get_start_time (reader); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_MARK) - { - const SysprofCaptureMark *ev; - Interval *iv; - - if (!(ev = sysprof_capture_reader_read_mark (reader))) - break; - - if (strcmp (category, ev->group) != 0 || - strcmp (name, ev->name) != 0) - continue; - - g_array_set_size (intervals, intervals->len + 1); - - iv = &g_array_index (intervals, Interval, intervals->len - 1); - iv->begin = ev->frame.time; - iv->end = ev->frame.time + ev->duration; - iv->allocated = 0; - } - else if (!sysprof_capture_reader_skip (reader)) - break; - } - - g_array_sort (intervals, compare_interval); - - sysprof_capture_reader_reset (reader); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev; - Interval *iv; - - if (!(ev = sysprof_capture_reader_read_allocation (reader))) - break; - - if (ev->alloc_size <= 0) - continue; - - if (!(iv = find_time (intervals, ev->frame.time))) - continue; - - iv->allocated += ev->alloc_size; - } - else if (!sysprof_capture_reader_skip (reader)) - break; - } - - for (guint i = 0; i < intervals->len; i++) - { - const Interval *iv = &g_array_index (intervals, Interval, i); - g_autofree gchar *size = NULL; - gdouble t1; - gdouble t2; - - if (iv->allocated <= 0) - continue; - - st.total += iv->allocated; - st.min = MIN (st.min, iv->allocated); - st.max = MAX (st.max, iv->allocated); - - size = g_format_size_full (iv->allocated, G_FORMAT_SIZE_IEC_UNITS); - - t1 = (iv->begin - begin) / (gdouble)SYSPROF_NSEC_PER_SEC; - t2 = (iv->end - begin) / (gdouble)SYSPROF_NSEC_PER_SEC; - - g_print ("%lf-%lf: %s\n", t1, t2, size); - } - - if (intervals->len) - { - const Interval *iv = &g_array_index (intervals, Interval, intervals->len/2); - g_autofree gchar *minstr = g_format_size_full (st.min, G_FORMAT_SIZE_IEC_UNITS); - g_autofree gchar *maxstr = g_format_size_full (st.max, G_FORMAT_SIZE_IEC_UNITS); - g_autofree gchar *avgstr = g_format_size_full (st.total/(gdouble)intervals->len, G_FORMAT_SIZE_IEC_UNITS); - g_autofree gchar *medstr = g_format_size_full (iv->allocated, G_FORMAT_SIZE_IEC_UNITS); - - g_print ("Min: %s, Max: %s, Avg: %s, Median: %s\n", minstr, maxstr, avgstr, medstr); - } -} - -gint -main (gint argc, - gchar *argv[]) -{ - SysprofCaptureReader *reader; - const gchar *filename; - const gchar *category; - const gchar *name; - - if (argc < 4) - { - g_printerr ("usage: %s FILENAME MARK_CATEGORY MARK_NAME\n", argv[0]); - return EXIT_FAILURE; - } - - filename = argv[1]; - category = argv[2]; - name = argv[3]; - - /* Set up gettext translations */ - setlocale (LC_ALL, ""); - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); - - if (!(reader = sysprof_capture_reader_new (filename))) - { - int errsv = errno; - g_printerr ("%s\n", g_strerror (errsv)); - return EXIT_FAILURE; - } - - allocs_within_mark (reader, category, name); - - sysprof_capture_reader_unref (reader); - - return EXIT_SUCCESS; -} diff --git a/src/tests/list-all-maps.c b/src/tests/list-all-maps.c deleted file mode 100644 index e53ea6b9..00000000 --- a/src/tests/list-all-maps.c +++ /dev/null @@ -1,358 +0,0 @@ -/* list-all-maps.c - * - * Copyright 2021 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include - -#include "../libsysprof/sysprof-helpers.h" -#include "../libsysprof/sysprof-mountinfo.h" -#include "../libsysprof/sysprof-path-resolver.h" -#include "../libsysprof/sysprof-podman.h" - -typedef enum { - PROCESS_KIND_HOST, - PROCESS_KIND_FLATPAK, - PROCESS_KIND_PODMAN, -} ProcessKind; - -typedef struct -{ - ProcessKind kind : 2; - int pid : 29; - char *kind_identifier; - char *mountinfo; - char *comm; - GArray *maps; - GArray *overlays; -} ProcessInfo; - -typedef struct -{ - char *on_host; - char *in_process; -} ProcessOverlay; - -typedef struct -{ - char *file; - SysprofCaptureAddress start; - SysprofCaptureAddress end; - SysprofCaptureAddress offset; - ino_t inode; -} ProcessMap; - -static const char * -kind_to_string (ProcessKind kind) -{ - switch (kind) - { - case PROCESS_KIND_HOST: - return "Host"; - case PROCESS_KIND_PODMAN: - return "Podman"; - case PROCESS_KIND_FLATPAK: - return "Flatpak"; - default: - return "Unknown"; - } -} - -static void -process_overlay_clear (ProcessOverlay *overlay) -{ - g_free (overlay->on_host); - g_free (overlay->in_process); -} - -static void -process_info_clear (ProcessInfo *info) -{ - g_free (info->kind_identifier); - g_free (info->mountinfo); - g_free (info->comm); - g_clear_pointer (&info->maps, g_array_unref); - g_clear_pointer (&info->overlays, g_array_unref); -} - -static void -process_map_clear (ProcessMap *map) -{ - g_free (map->file); -} - -static ProcessKind -get_process_kind_from_cgroup (int pid, - const char *cgroup, - char **identifier) -{ - static GRegex *podman_regex; - static GRegex *flatpak_regex; - - g_autoptr(GMatchInfo) podman_match = NULL; - g_autoptr(GMatchInfo) flatpak_match = NULL; - - if G_UNLIKELY (podman_regex == NULL) - podman_regex = g_regex_new ("libpod-([a-z0-9]{64})\\.scope", G_REGEX_OPTIMIZE, 0, NULL); - - if G_UNLIKELY (flatpak_regex == NULL) - flatpak_regex = g_regex_new ("app-flatpak-([a-zA-Z_\\-\\.]+)-[0-9]+\\.scope", G_REGEX_OPTIMIZE, 0, NULL); - - if (g_regex_match (podman_regex, cgroup, 0, &podman_match)) - { - if (identifier != NULL) - *identifier = g_match_info_fetch (podman_match, 1); - return PROCESS_KIND_PODMAN; - } - else if (g_regex_match (flatpak_regex, cgroup, 0, &flatpak_match)) - { - if (identifier != NULL) - *identifier = g_match_info_fetch (flatpak_match, 1); - return PROCESS_KIND_FLATPAK; - } - else - { - if (identifier != NULL) - *identifier = NULL; - return PROCESS_KIND_HOST; - } -} - -static void -process_info_populate_podman_overlays (ProcessInfo *proc, - SysprofPodman *podman) -{ - g_auto(GStrv) layers = NULL; - - g_assert (proc != NULL); - g_assert (proc->kind == PROCESS_KIND_PODMAN); - - if ((layers = sysprof_podman_get_layers (podman, proc->kind_identifier))) - { - for (guint i = 0; layers[i]; i++) - { - ProcessOverlay overlay; - g_autofree char *path = g_build_filename (g_get_user_data_dir (), - "containers", - "storage", - "overlay", - layers[i], - "diff", - NULL); - - /* XXX: this really only supports one layer */ - overlay.in_process = g_strdup ("/"); - overlay.on_host = g_steal_pointer (&path); - - if (proc->overlays == NULL) - { - proc->overlays = g_array_new (FALSE, FALSE, sizeof (ProcessOverlay)); - g_array_set_clear_func (proc->overlays, (GDestroyNotify)process_overlay_clear); - } - - g_array_append_val (proc->overlays, overlay); - } - } -} - -static void -process_info_populate_maps (ProcessInfo *proc, - const char *maps) -{ - g_auto(GStrv) lines = NULL; - - g_assert (proc != NULL); - g_assert (maps != NULL); - - if (proc->maps == NULL) - { - proc->maps = g_array_new (FALSE, FALSE, sizeof (ProcessMap)); - g_array_set_clear_func (proc->maps, (GDestroyNotify)process_map_clear); - } - - lines = g_strsplit (maps, "\n", 0); - - for (guint i = 0; lines[i] != NULL; i++) - { - ProcessMap map; - gchar file[512]; - gulong start; - gulong end; - gulong offset; - gulong inode; - int r; - - r = sscanf (lines[i], - "%lx-%lx %*15s %lx %*x:%*x %lu %512s", - &start, &end, &offset, &inode, file); - file[sizeof file - 1] = '\0'; - - if (r != 5) - continue; - - if (strcmp ("[vdso]", file) == 0) - { - /* - * Søren Sandmann Pedersen says: - * - * For the vdso, the kernel reports 'offset' as the - * the same as the mapping address. This doesn't make - * any sense to me, so we just zero it here. There - * is code in binfile.c (read_inode) that returns 0 - * for [vdso]. - */ - offset = 0; - inode = 0; - } - - map.file = g_strdup (file); - map.start = start; - map.end = end; - map.offset = offset; - map.inode = inode; - - g_array_append_val (proc->maps, map); - } -} - -static gboolean -process_info_populate (ProcessInfo *proc, - GVariant *variant, - SysprofPodman *podman) -{ - const char *str; - int pid; - - g_assert (proc != NULL); - g_assert (proc->pid == 0); - g_assert (variant != NULL); - g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT)); - - if (g_variant_lookup (variant, "pid", "i", &pid)) - proc->pid = pid; - else - return FALSE; - - if (g_variant_lookup (variant, "comm", "&s", &str)) - proc->comm = g_strdup (str); - - if (g_variant_lookup (variant, "cgroup", "&s", &str)) - proc->kind = get_process_kind_from_cgroup (pid, str, &proc->kind_identifier); - else - proc->kind = PROCESS_KIND_HOST; - - if (proc->kind == PROCESS_KIND_PODMAN) - process_info_populate_podman_overlays (proc, podman); - - if (g_variant_lookup (variant, "mountinfo", "&s", &str)) - proc->mountinfo = g_strdup (str); - - if (g_variant_lookup (variant, "maps", "&s", &str)) - process_info_populate_maps (proc, str); - - return TRUE; -} - -static gboolean -list_all_maps (GError **error) -{ - SysprofHelpers *helpers = sysprof_helpers_get_default (); - g_autoptr(SysprofPodman) podman = NULL; - g_autofree char *mounts = NULL; - g_autoptr(GVariant) info = NULL; - g_autoptr(GArray) processes = NULL; - GVariant *value; - GVariantIter iter; - - if (!sysprof_helpers_get_process_info (helpers, "pid,maps,mountinfo,cmdline,comm,cgroup", FALSE, NULL, &info, error)) - return FALSE; - - if (!sysprof_helpers_get_proc_file (helpers, "/proc/mounts", NULL, &mounts, error)) - return FALSE; - - podman = sysprof_podman_snapshot_current_user (); - - processes = g_array_new (FALSE, TRUE, sizeof (ProcessInfo)); - g_array_set_clear_func (processes, (GDestroyNotify)process_info_clear); - - g_variant_iter_init (&iter, info); - while (g_variant_iter_loop (&iter, "@a{sv}", &value)) - { - ProcessInfo *proc; - - g_array_set_size (processes, processes->len + 1); - proc = &g_array_index (processes, ProcessInfo, processes->len - 1); - - if (!process_info_populate (proc, value, podman)) - g_array_set_size (processes, processes->len - 1); - } - - for (guint i = 0; i < processes->len; i++) - { - const ProcessInfo *proc = &g_array_index (processes, ProcessInfo, i); - g_autoptr(SysprofPathResolver) resolver = _sysprof_path_resolver_new (mounts, proc->mountinfo); - - if (proc->maps == NULL || proc->maps->len == 0) - continue; - - g_print ("Process %d", proc->pid); - if (proc->kind != PROCESS_KIND_HOST) - g_print (" (%s)", kind_to_string (proc->kind)); - g_print (" %s\n", proc->comm); - - if (proc->overlays != NULL) - { - for (guint j = 0; j < proc->overlays->len; j++) - { - const ProcessOverlay *overlay = &g_array_index (proc->overlays, ProcessOverlay, j); - _sysprof_path_resolver_add_overlay (resolver, overlay->in_process, overlay->on_host, j); - } - } - - for (guint j = 0; j < proc->maps->len; j++) - { - const ProcessMap *map = &g_array_index (proc->maps, ProcessMap, j); - g_autofree char *path = _sysprof_path_resolver_resolve (resolver, map->file); - - if (path != NULL) - { - g_print (" %s", path); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) - g_print (" (missing)"); - g_print ("\n"); - } - } - - g_print ("\n"); - } - - return TRUE; -} - -int -main (int argc, - char *argv[]) -{ - g_autoptr(GError) error = NULL; - if (!list_all_maps (&error)) - g_printerr ("%s\n", error->message); - return error ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/tests/list-maps.c b/src/tests/list-maps.c deleted file mode 100644 index dbdf5045..00000000 --- a/src/tests/list-maps.c +++ /dev/null @@ -1,103 +0,0 @@ -/* list-maps.c - * - * Copyright 2021 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include -#include -#include - -#include "sysprof-elf-symbol-resolver-private.h" - -static ino_t -read_inode (const char *filename) -{ - struct stat statbuf; - - if (filename == NULL) - return (ino_t)-1; - - if (strcmp (filename, "[vdso]") == 0) - return (ino_t)0; - - if (stat (filename, &statbuf) < 0) - return (ino_t)-1; - - return statbuf.st_ino; -} - -static void -list_maps (const char *filename) -{ - g_autoptr(SysprofCaptureReader) reader = NULL; - g_autoptr(SysprofSymbolResolver) resolver = NULL; - SysprofCaptureFrameType type; - - if (!(reader = sysprof_capture_reader_new (filename))) - return; - - resolver = sysprof_elf_symbol_resolver_new (); - sysprof_symbol_resolver_load (resolver, reader); - sysprof_capture_reader_reset (reader); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_MAP) - { - const SysprofCaptureMap *ev = sysprof_capture_reader_read_map (reader); - g_autofree char *resolved = _sysprof_elf_symbol_resolver_resolve_path (SYSPROF_ELF_SYMBOL_RESOLVER (resolver), ev->frame.pid, ev->filename); - const char *kind = _sysprof_elf_symbol_resolver_get_pid_kind (SYSPROF_ELF_SYMBOL_RESOLVER (resolver), ev->frame.pid); - ino_t inode = read_inode (resolved ? resolved : ev->filename); - - g_print ("PID %u (%s): ", ev->frame.pid, kind); - g_print ("%s => %s", ev->filename, resolved ? resolved : ev->filename); - - if (inode == (ino_t)-1) - g_print (" (missing)"); - else if (ev->inode != 0 && inode != ev->inode) - g_print (" (Inode mismatch, expected %lu got %lu)", - (gulong)ev->inode, (gulong)inode); - - g_print ("\n"); - } - else - { - if (!sysprof_capture_reader_skip (reader)) - break; - } - } -} - -int -main (int argc, - char *argv[]) -{ - if (argc == 1) - { - g_printerr ("usage: %s CAPTURE_FILE...\n", argv[0]); - return 0; - } - - for (guint i = 1; i < argc; i++) - list_maps (argv[i]); - - return 0; -} diff --git a/src/tests/memory-stack-stash.c b/src/tests/memory-stack-stash.c deleted file mode 100644 index f5dea02f..00000000 --- a/src/tests/memory-stack-stash.c +++ /dev/null @@ -1,91 +0,0 @@ -/* memory-stack-stash.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "../stackstash.h" - -static void -memory_stack_stash (SysprofCaptureReader *reader) -{ - SysprofCaptureFrameType type; - StackStash *stash = stack_stash_new (NULL); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_ALLOCATION) - { - const SysprofCaptureAllocation *ev = sysprof_capture_reader_read_allocation (reader); - - if (ev == NULL) - break; - - if (ev->alloc_size > 0) - stack_stash_add_trace (stash, ev->addrs, ev->n_addrs, ev->alloc_size); - } - else - { - if (!sysprof_capture_reader_skip (reader)) - break; - } - } - - stack_stash_unref (stash); -} - -gint -main (gint argc, - gchar *argv[]) -{ - SysprofCaptureReader *reader; - const gchar *filename = argv[1]; - - if (argc < 2) - { - g_printerr ("usage: %s FILENAME\n", argv[0]); - return EXIT_FAILURE; - } - - /* Set up gettext translations */ - setlocale (LC_ALL, ""); - bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); - bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - textdomain (GETTEXT_PACKAGE); - - if (!(reader = sysprof_capture_reader_new (filename))) - { - int errsv = errno; - g_printerr ("%s\n", g_strerror (errsv)); - return EXIT_FAILURE; - } - - memory_stack_stash (reader); - - sysprof_capture_reader_unref (reader); - - return EXIT_SUCCESS; -} diff --git a/src/tests/meson.build b/src/tests/meson.build deleted file mode 100644 index 2dbc717b..00000000 --- a/src/tests/meson.build +++ /dev/null @@ -1,153 +0,0 @@ -test_env = [ - 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()), - 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), - 'G_DEBUG=gc-friendly', - 'GSETTINGS_BACKEND=memory', - 'MALLOC_CHECK_=2', - 'NO_AT_BRIDGE=1', -] - -test_cflags = [ - '-DTEST_DATA_DIR="@0@/data/"'.format(meson.current_source_dir()), - '-DSYSPROF_COMPILATION=1', -] - -test_capture_deps = [ - dependency('glib-2.0'), - libsysprof_capture_dep, -] - -test_capture = executable('test-capture', 'test-capture.c', - c_args: test_cflags, - dependencies: test_capture_deps, -) - -test_capture_cursor = executable('test-capture-cursor', 'test-capture-cursor.c', - c_args: test_cflags, - dependencies: test_capture_deps, -) - -test_mapped_ring_buffer = executable('test-mapped-ring-buffer', 'test-mapped-ring-buffer.c', - c_args: test_cflags, - dependencies: test_capture_deps, -) - -find_temp_allocs = executable('find-temp-allocs', 'find-temp-allocs.c', - c_args: test_cflags, - dependencies: test_capture_deps, -) - -test('test-capture', test_capture, env: test_env) -test('test-capture-cursor', test_capture_cursor, env: test_env) -test('test-mapped-ring-buffer', test_mapped_ring_buffer, env: test_env) - -if get_option('libsysprof') - test_deps = [ - libsysprof_static_dep, - ] - - test_addr_map = executable('test-addr-map', 'test-addr-map.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - test_addr_decode = executable('test-addr-decode', 'test-addr-decode.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - test_capture_model = executable('test-capture-model', 'test-capture-model.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - test_mountinfo = executable('test-mountinfo', 'test-mountinfo.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - test_flatpak = executable('test-flatpak', 'test-flatpak.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - test_resolvers = executable('test-resolvers', 'test-resolvers.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - allocs_by_size = executable('allocs-by-size', 'allocs-by-size.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - allocs_within_mark = executable('allocs-within-mark', 'allocs-within-mark.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - cross_thread_frees = executable('cross-thread-frees', 'cross-thread-frees.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - memory_stack_stash = executable('memory-stack-stash', 'memory-stack-stash.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - read_build_id = executable('read-build-id', 'read-build-id.c', - c_args: test_cflags, - dependencies: test_deps, - ) - - show_page_usage = executable('show-page-usage', 'show-page-usage.c', - c_args: test_cflags, - dependencies: test_deps + [dependency('cairo')], - ) - - list_pid_maps = executable('list-all-maps', 'list-all-maps.c', - c_args: test_cflags, - dependencies: [libsysprof_static_dep], - include_directories: include_directories('..'), - ) - - list_maps = executable('list-maps', 'list-maps.c', - c_args: test_cflags, - dependencies: [libsysprof_static_dep], - include_directories: include_directories('..'), - ) - - if get_option('gtk') - test_ui_deps = [ - libsysprof_dep, - libsysprof_ui_dep, - dependency('gtk4', version: gtk_req_version), - dependency('pangoft2', required: false), - ] - - test_model_filter = executable('test-model-filter', 'test-model-filter.c', - c_args: test_cflags, - dependencies: test_ui_deps, - ) - - test_process_model = executable('test-process-model', 'test-process-model.c', - c_args: test_cflags, - dependencies: test_ui_deps, - ) - - test_zoom = executable('test-zoom', - ['test-zoom.c', '../libsysprof-ui/sysprof-zoom-manager.c'], - c_args: test_cflags, - dependencies: test_ui_deps, - ) - - test_capture_view = executable('test-capture-view', 'test-capture-view.c', - c_args: test_cflags, - dependencies: test_ui_deps, - ) - - test('test-model-filter', test_model_filter, env: test_env) - test('test-zoom', test_zoom, env: test_env) - endif -endif diff --git a/src/tests/show-page-usage.c b/src/tests/show-page-usage.c deleted file mode 100644 index e861578d..00000000 --- a/src/tests/show-page-usage.c +++ /dev/null @@ -1,198 +0,0 @@ -/* show-page-usage.c - * - * Copyright 2020 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -static GMainLoop *main_loop; - -static gint -u64_compare (gconstpointer a, - gconstpointer b) -{ - const guint64 *aptr = a; - const guint64 *bptr = b; - - if (*aptr < *bptr) - return -1; - else if (*aptr > *bptr) - return 1; - else - return 0; -} - -static void -generate_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - SysprofProfile *profile = (SysprofProfile *)object; - g_autoptr(GError) error = NULL; - GHashTable *seen; - GHashTableIter iter; - cairo_t *cr; - cairo_surface_t *surface; - GArray *ar; - raxIterator it; - rax *r; - gpointer k,v; - - g_assert (SYSPROF_IS_MEMPROF_PROFILE (profile)); - g_assert (G_IS_ASYNC_RESULT (result)); - - if (!sysprof_profile_generate_finish (profile, result, &error)) - { - g_printerr ("%s\n", error->message); - exit (EXIT_FAILURE); - } - - r = sysprof_memprof_profile_get_native (SYSPROF_MEMPROF_PROFILE (profile)); - seen = g_hash_table_new (NULL, NULL); - - raxStart (&it, r); - raxSeek (&it, "^", NULL, 0); - while (raxNext (&it)) - { - guint64 page; - guint64 addr; - - memcpy (&addr, it.key, sizeof addr); - page = addr / 4096; - - if (g_hash_table_contains (seen, GSIZE_TO_POINTER (page))) - continue; - - g_hash_table_insert (seen, GSIZE_TO_POINTER (page), NULL); - } - raxStop (&it); - - ar = g_array_sized_new (FALSE, FALSE, sizeof (guint64), g_hash_table_size (seen)); - - g_hash_table_iter_init (&iter, seen); - while (g_hash_table_iter_next (&iter, &k, &v)) - { - guint64 key = GPOINTER_TO_SIZE (k); - - g_array_append_val (ar, key); - } - - g_array_sort (ar, u64_compare); - - for (guint i = 0; i < ar->len; i++) - { - guint64 key = g_array_index (ar, guint64, i); - - g_hash_table_insert (seen, GSIZE_TO_POINTER (key), GSIZE_TO_POINTER (i)); - } - - g_printerr ("We have %u pages to graph\n", ar->len); - - surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, ar->len, (4096/16)); - cr = cairo_create (surface); - - cairo_set_line_width (cr, 1.0); - cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); - - cairo_set_source_rgb (cr, 1, 1, 1); - cairo_rectangle (cr, 0, 0, ar->len, (4096/16)); - cairo_fill (cr); - - cairo_set_source_rgb (cr, 0, 0, 0); - - cairo_scale (cr, 1.0, 1.0/16.0); - cairo_translate (cr, .5, .5); - - raxStart (&it, r); - raxSeek (&it, "^", NULL, 0); - while (raxNext (&it)) - { - guint64 page; - guint64 addr; - guint64 size; - guint x; - guint y; - - memcpy (&addr, it.key, sizeof addr); - page = addr / 4096; - size = GPOINTER_TO_SIZE (it.data); - - x = GPOINTER_TO_UINT (g_hash_table_lookup (seen, GSIZE_TO_POINTER (page))); - y = addr % 4096; - - /* TODO: Need size */ - - cairo_move_to (cr, x, y); - cairo_line_to (cr, x, y+size); - } - raxStop (&it); - - cairo_stroke (cr); - - cairo_surface_write_to_png (surface, "memory.png"); - - cairo_destroy (cr); - cairo_surface_destroy (surface); - - g_array_unref (ar); - g_hash_table_unref (seen); - - g_main_loop_quit (main_loop); -} - -gint -main (gint argc, - gchar *argv[]) -{ - SysprofCaptureReader *reader; - const gchar *filename = argv[1]; - g_autoptr(SysprofProfile) memprof = NULL; - - if (argc < 2) - { - g_printerr ("usage: %s FILENAME\n", argv[0]); - return EXIT_FAILURE; - } - - main_loop = g_main_loop_new (NULL, FALSE); - - if (!(reader = sysprof_capture_reader_new (filename))) - { - int errsv = errno; - g_printerr ("%s\n", g_strerror (errsv)); - return EXIT_FAILURE; - } - - memprof = sysprof_memprof_profile_new (); - sysprof_profile_set_reader (memprof, reader); - sysprof_profile_generate (memprof, NULL, generate_cb, NULL); - - g_main_loop_run (main_loop); - g_main_loop_unref (main_loop); - - sysprof_capture_reader_unref (reader); - - return EXIT_SUCCESS; -} diff --git a/src/tests/test-addr-decode.c b/src/tests/test-addr-decode.c deleted file mode 100644 index bfe7db55..00000000 --- a/src/tests/test-addr-decode.c +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include -#include - -#include "sysprof-platform.h" -#include "sysprof-capture-symbol-resolver.h" - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(SysprofCaptureReader) reader = NULL; - g_autoptr(SysprofSymbolResolver) resolver = NULL; - SysprofCaptureFrameType type; - - if (argc != 2) - { - g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); - return 1; - } - - if (!(reader = sysprof_capture_reader_new (argv[1]))) - { - int errsv = errno; - g_printerr ("%s\n", g_strerror (errsv)); - return 1; - } - - resolver = sysprof_capture_symbol_resolver_new (); - sysprof_symbol_resolver_load (resolver, reader); - - sysprof_capture_reader_reset (reader); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_SAMPLE) - { - const SysprofCaptureSample *sample; - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - - if ((sample = sysprof_capture_reader_read_sample (reader))) - { - for (guint i = 0; i < sample->n_addrs; i++) - { - g_autofree gchar *name = NULL; - SysprofAddressContext context; - GQuark tag = 0; - - if (sysprof_address_is_context_switch (sample->addrs[i], &context)) - { - last_context = context; - continue; - } - - name = sysprof_symbol_resolver_resolve_with_context (resolver, - sample->frame.time, - sample->frame.pid, - last_context, - sample->addrs[i], - &tag); - - if (tag) - g_print ("%u: %s [%s]\n", - i, - name ? name : "-- missing --", - g_quark_to_string (tag)); - else - g_print ("%u: %s\n", - i, - name ? name : "-- missing --"); - } - - g_print ("======\n"); - - continue; - } - } - - if (!sysprof_capture_reader_skip (reader)) - break; - } - - return 0; -} diff --git a/src/tests/test-addr-map.c b/src/tests/test-addr-map.c deleted file mode 100644 index 6a771586..00000000 --- a/src/tests/test-addr-map.c +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include -#include -#include - -#include "sysprof-platform.h" -#include "sysprof-symbol-map.h" - -static GMainLoop *main_loop; - -static void * -resolve_in_thread (gpointer data) -{ - SysprofCaptureReader *reader = data; - g_autoptr(SysprofSymbolResolver) kernel = NULL; - g_autoptr(SysprofSymbolResolver) elf = NULL; - SysprofCaptureFrameType type; - SysprofSymbolMap *map; - gboolean r; - int fd; - - g_assert (reader != NULL); - - map = sysprof_symbol_map_new (); - kernel = sysprof_kernel_symbol_resolver_new (); - elf = sysprof_elf_symbol_resolver_new (); - - sysprof_symbol_map_add_resolver (map, kernel); - sysprof_symbol_map_add_resolver (map, elf); - - sysprof_symbol_map_resolve (map, reader); - - fd = sysprof_memfd_create ("decode-test"); - g_assert_cmpint (fd, !=, -1); - - r = sysprof_symbol_map_serialize (map, fd); - g_assert_true (r); - sysprof_symbol_map_free (map); - - /* Reset some state */ - sysprof_capture_reader_reset (reader); - lseek (fd, 0, SEEK_SET); - - /* Now desrialize it */ - map = sysprof_symbol_map_new (); - sysprof_symbol_map_deserialize (map, G_BYTE_ORDER, fd); - - /* Now try to print some stack traces */ - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type == SYSPROF_CAPTURE_FRAME_SAMPLE) - { - const SysprofCaptureSample *sample = NULL; - - if (!(sample = sysprof_capture_reader_read_sample (reader))) - break; - - for (guint j = 0; j < sample->n_addrs; j++) - { - const gchar *name; - GQuark tag; - - if (!(name = sysprof_symbol_map_lookup (map, sample->frame.time, sample->frame.pid, sample->addrs[j], &tag))) - name = "Unknown symbol"; - - g_print ("%u: %s\n", j, name); - } - - g_print ("======\n"); - } - else if (!sysprof_capture_reader_skip (reader)) - break; - } - - - sysprof_symbol_map_free (map); - - close (fd); - g_main_loop_quit (main_loop); - return NULL; -} - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(SysprofCaptureReader) reader = NULL; - - if (argc != 2) - { - g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); - return 1; - } - - if (!(reader = sysprof_capture_reader_new (argv[1]))) - { - int errsv = errno; - g_printerr ("%s\n", g_strerror (errsv)); - return 1; - } - - main_loop = g_main_loop_new (NULL, FALSE); - g_thread_new ("reader-thread", resolve_in_thread, reader); - g_main_loop_run (main_loop); - - return 0; -} diff --git a/src/tests/test-capture-model.c b/src/tests/test-capture-model.c deleted file mode 100644 index aca52f0b..00000000 --- a/src/tests/test-capture-model.c +++ /dev/null @@ -1,56 +0,0 @@ -#include -#include - -#include - -int -main (int argc, - char *argv[]) -{ - SysprofCaptureModel *model; - const char *filename; - GError *error = NULL; - guint n_items; - int fd; - - sysprof_clock_init (); - - if (argc < 2) - { - g_printerr ("usage: %s FILENAME\n", argv[0]); - return 1; - } - - filename = argv[1]; - fd = open (filename, O_RDONLY|O_CLOEXEC); - - if (fd == -1) - { - g_printerr ("Failed to open %s: %s\n", - filename, g_strerror (errno)); - return 1; - } - - if (!(model = sysprof_capture_model_new_from_fd (fd, &error))) - { - g_printerr ("Failed to load %s: %s\n", - filename, error->message); - return 1; - } - - n_items = g_list_model_get_n_items (G_LIST_MODEL (model)); - - g_print ("%u frames\n", n_items); - - for (guint i = 0; i < n_items; i++) - { - SysprofCaptureFrameObject *obj = g_list_model_get_item (G_LIST_MODEL (model), i); - - g_clear_object (&obj); - } - - close (fd); - g_clear_object (&model); - - return 0; -} diff --git a/src/tests/test-capture-view.c b/src/tests/test-capture-view.c deleted file mode 100644 index 1331e828..00000000 --- a/src/tests/test-capture-view.c +++ /dev/null @@ -1,66 +0,0 @@ -/* test-capture-view.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include - -gint -main (gint argc, - gchar *argv[]) -{ - GtkWindow *window; - SysprofDisplay *view; - SysprofCaptureReader *reader; - g_autoptr(GError) error = NULL; - GMainLoop *main_loop; - - gtk_init (); - - if (argc != 2) - { - g_printerr ("usage: %s CAPTURE.syscap\n", argv[0]); - return 1; - } - - if (!(reader = sysprof_capture_reader_new_with_error (argv[1], &error))) - { - g_printerr ("Failed to load reader: %s\n", error->message); - return 1; - } - - main_loop = g_main_loop_new (NULL, FALSE); - - window = g_object_new (GTK_TYPE_WINDOW, - "title", "SysprofDisplay", - "default-width", 800, - "default-height", 600, - NULL); - view = g_object_new (SYSPROF_TYPE_DISPLAY, - "visible", TRUE, - NULL); - gtk_window_set_child (GTK_WINDOW (window), GTK_WIDGET (view)); - - sysprof_display_load_async (view, reader, NULL, NULL, NULL); - - g_signal_connect_swapped (window, "close-request", G_CALLBACK (g_main_loop_quit), main_loop); - gtk_window_present (GTK_WINDOW (window)); - g_main_loop_run (main_loop); - - return 0; -} diff --git a/src/tests/test-flatpak.c b/src/tests/test-flatpak.c deleted file mode 100644 index 52abfba4..00000000 --- a/src/tests/test-flatpak.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "sysprof-flatpak.h" - -gint -main (gint argc, - gchar *argv[]) -{ - g_auto(GStrv) debug_dirs = sysprof_flatpak_debug_dirs (); - - for (guint i = 0; debug_dirs[i]; i++) - g_print ("%s\n", debug_dirs[i]); - - return 0; -} diff --git a/src/tests/test-kallsyms.c b/src/tests/test-kallsyms.c deleted file mode 100644 index 4b8dff50..00000000 --- a/src/tests/test-kallsyms.c +++ /dev/null @@ -1,34 +0,0 @@ -#include -#include -#include - -#include "sysprof-kallsyms.h" - -int -main (gint argc, - gchar *argv[]) -{ - g_autoptr(SysprofKallsyms) kallsyms = NULL; - const gchar *name; - SysprofAddress addr = 0; - guint8 type; - - if (argc < 2) - { - g_printerr ("usage: %s FILE\n", argv[0]); - return EXIT_FAILURE; - } - - kallsyms = sysprof_kallsyms_new (argv[1]); - - while (sysprof_kallsyms_next (kallsyms, &name, &addr, &type)) - { - g_assert (name != NULL); - g_assert (addr != 0); - g_assert (type != 0); - - g_print ("%s %"G_GUINT64_FORMAT"x\n", name, addr); - } - - return EXIT_SUCCESS; -} diff --git a/src/tests/test-model-filter.c b/src/tests/test-model-filter.c deleted file mode 100644 index dbe87c05..00000000 --- a/src/tests/test-model-filter.c +++ /dev/null @@ -1,251 +0,0 @@ -#include -#include - -#define TEST_TYPE_ITEM (test_item_get_type()) - -struct _TestItem -{ - GObject p; - guint n; -}; - -G_DECLARE_FINAL_TYPE (TestItem, test_item, TEST, ITEM, GObject) -G_DEFINE_TYPE (TestItem, test_item, G_TYPE_OBJECT) - -static void -test_item_class_init (TestItemClass *klass) -{ -} - -static void -test_item_init (TestItem *self) -{ -} - -static TestItem * -test_item_new (guint n) -{ - TestItem *item; - - item = g_object_new (TEST_TYPE_ITEM, NULL); - item->n = n; - - return item; -} - -static gboolean -filter_func1 (GObject *object, - gpointer user_data) -{ - return (TEST_ITEM (object)->n & 1) == 0; -} - -static gboolean -filter_func2 (GObject *object, - gpointer user_data) -{ - return (TEST_ITEM (object)->n & 1) == 1; -} - -static void -test_basic (void) -{ - GListStore *model; - SysprofModelFilter *filter; - TestItem *item; - guint i; - - model = g_list_store_new (TEST_TYPE_ITEM); - g_assert (model); - - filter = sysprof_model_filter_new (G_LIST_MODEL (model)); - g_assert (filter); - - for (i = 0; i < 1000; i++) - { - g_autoptr(TestItem) val = test_item_new (i); - - g_list_store_append (model, val); - } - - g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (model))); - g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - g_assert_cmpint (1000, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - sysprof_model_filter_set_filter_func (filter, filter_func1, NULL, NULL); - g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - for (i = 0; i < 500; i++) - { - g_autoptr(TestItem) ele = g_list_model_get_item (G_LIST_MODEL (filter), i); - - g_assert (TEST_IS_ITEM (ele)); - g_assert (filter_func1 (G_OBJECT (ele), NULL)); - } - - for (i = 0; i < 1000; i += 2) - g_list_store_remove (model, 998 - i); - - g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (model))); - g_assert_cmpint (0, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - sysprof_model_filter_set_filter_func (filter, NULL, NULL, NULL); - g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - sysprof_model_filter_set_filter_func (filter, filter_func2, NULL, NULL); - g_assert_cmpint (500, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - { - g_autoptr(TestItem) freeme = test_item_new (1001); - g_list_store_append (model, freeme); - } - - for (i = 0; i < 500; i++) - g_list_store_remove (model, 0); - - g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (model))); - g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - sysprof_model_filter_set_filter_func (filter, NULL, NULL, NULL); - g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (model))); - g_assert_cmpint (1, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - item = g_list_model_get_item (G_LIST_MODEL (filter), 0); - g_assert (item); - g_assert (TEST_IS_ITEM (item)); - g_assert_cmpint (item->n, ==, 1001); - g_clear_object (&item); - - g_clear_object (&model); - g_clear_object (&filter); -} - -static gboolean -filter_keyword_cb (GObject *object, - gpointer user_data) -{ - SysprofProcessModelItem *item = SYSPROF_PROCESS_MODEL_ITEM (object); - const gchar *haystack = sysprof_process_model_item_get_command_line (item); - const gchar *needle = user_data; - - if (haystack && needle) - return strstr (haystack, needle) != NULL; - - return FALSE; -} - -static void -test_process (void) -{ - SysprofProcessModel *model = sysprof_process_model_new (); - SysprofModelFilter *filter; - static gchar *searches[] = { - "a", "b", "foo", "bar", "gnome", "gnome-test", - "libexec", "/", ":", "gsd-", - }; - - filter = sysprof_model_filter_new (G_LIST_MODEL (model)); - - sysprof_process_model_set_no_proxy (model, TRUE); - sysprof_process_model_reload (model); - - for (guint i = 0; i < G_N_ELEMENTS (searches); i++) - { - const gchar *needle = searches[i]; - guint n_items; - - sysprof_model_filter_set_filter_func (filter, filter_keyword_cb, g_strdup (needle), g_free); - - n_items = g_list_model_get_n_items (G_LIST_MODEL (filter)); - - for (guint j = 0; j < n_items; j++) - { - g_autoptr(SysprofProcessModelItem) item = g_list_model_get_item (G_LIST_MODEL (filter), j); - - g_assert (SYSPROF_IS_PROCESS_MODEL_ITEM (item)); - g_assert (filter_keyword_cb (G_OBJECT (item), (gchar *)needle)); - - //g_print ("%s: %s\n", needle, sysprof_process_model_item_get_command_line (item)); - } - } - - g_object_unref (filter); - g_object_unref (model); -} - -static guint last_n_added = 0; -static guint last_n_removed = 0; -static guint last_changed_position = 0; - -static void -model_items_changed_cb (SysprofModelFilter *filter, - guint position, - guint n_removed, - guint n_added, - GListModel *model) -{ - last_n_added = n_added; - last_n_removed = n_removed; - last_changed_position = position; -} - - -static void -filter_items_changed_cb (SysprofModelFilter *filter, - guint position, - guint n_removed, - guint n_added, - GListModel *model) -{ - g_assert_cmpint (n_added, ==, last_n_added); - g_assert_cmpint (n_removed, ==, last_n_removed); - g_assert_cmpint (position, ==, last_changed_position); -} - -static void -test_items_changed (void) -{ - SysprofModelFilter *filter; - GListStore *model; - guint i; - - model = g_list_store_new (TEST_TYPE_ITEM); - g_assert (model); - - g_signal_connect (model, "items-changed", G_CALLBACK (model_items_changed_cb), NULL); - - filter = sysprof_model_filter_new (G_LIST_MODEL (model)); - g_assert (filter); - - g_signal_connect_after (filter, "items-changed", G_CALLBACK (filter_items_changed_cb), model); - - /* The filter model is not filtered, so it must mirror whatever - * the child model does. In this case, test if the position of - * items-changed match. - */ - for (i = 0; i < 100; i++) - { - g_autoptr (TestItem) val = test_item_new (i); - g_list_store_append (model, val); - } - - g_assert_cmpint (100, ==, g_list_model_get_n_items (G_LIST_MODEL (model))); - g_assert_cmpint (100, ==, g_list_model_get_n_items (G_LIST_MODEL (filter))); - - for (i = 0; i < 100; i++) - g_list_store_remove (model, 0); - - g_clear_object (&model); - g_clear_object (&filter); -} - -gint -main (gint argc, - gchar *argv[]) -{ - g_test_init (&argc, &argv, NULL); - g_test_add_func ("/SysprofModelFilter/basic", test_basic); - g_test_add_func ("/SysprofModelFilter/process", test_process); - g_test_add_func ("/SysprofModelFilter/items-changed", test_items_changed); - return g_test_run (); -} diff --git a/src/tests/test-mountinfo.c b/src/tests/test-mountinfo.c deleted file mode 100644 index 7a835a20..00000000 --- a/src/tests/test-mountinfo.c +++ /dev/null @@ -1,58 +0,0 @@ -/* test-mountinfo.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "sysprof-mountinfo.h" - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(SysprofMountinfo) info = NULL; - g_autofree gchar *contents = NULL; - g_autoptr(GError) error = NULL; - g_autofree gchar *lookup = NULL; - gsize len; - - if (argc != 4) - { - g_printerr ("usage: %s MOUNTS MOUNTINFO_FILE PATH_TO_TRANSLATE\n", argv[0]); - return 1; - } - - info = sysprof_mountinfo_new (); - - if (!g_file_get_contents (argv[1], &contents, &len, &error)) - g_error ("%s", error->message); - sysprof_mountinfo_parse_mounts (info, contents); - g_free (g_steal_pointer (&contents)); - - if (!g_file_get_contents (argv[2], &contents, &len, &error)) - g_error ("%s", error->message); - sysprof_mountinfo_parse_mountinfo (info, contents); - - lookup = sysprof_mountinfo_translate (info, argv[3]); - - if (lookup) - g_print ("%s\n", lookup); - else - g_print ("%s\n", argv[3]); - - return 0; -} diff --git a/src/tests/test-process-model.c b/src/tests/test-process-model.c deleted file mode 100644 index f67e66b0..00000000 --- a/src/tests/test-process-model.c +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include -#include - -static GtkWidget *list; - -static GtkWidget * -create_row (gpointer item, - gpointer user_data) -{ - return sysprof_process_model_row_new (item); -} - -static gboolean -filter_cb (GObject *object, - gpointer user_data) -{ - const gchar *needle = user_data; - const gchar *command = sysprof_process_model_item_get_command_line (SYSPROF_PROCESS_MODEL_ITEM (object)); - - return needle == NULL || needle[0] == 0 || strstr (command, needle) != NULL; -} - -static void -on_entry_changed (GtkEntry *entry, - SysprofModelFilter *filter) -{ - const gchar *text; - - g_assert (GTK_IS_ENTRY (entry)); - g_assert (SYSPROF_IS_MODEL_FILTER (filter)); - - text = gtk_editable_get_text (GTK_EDITABLE (entry)); - sysprof_model_filter_set_filter_func (filter, filter_cb, g_strdup (text), g_free); - - gtk_list_box_bind_model (GTK_LIST_BOX (list), G_LIST_MODEL (filter), create_row, NULL, NULL); -} - -gint -main (gint argc, - gchar *argv[]) -{ - SysprofProcessModel *model; - SysprofModelFilter *filter; - GtkWidget *window; - GtkWidget *box; - GtkWidget *scroller; - GtkWidget *entry; - GMainLoop *main_loop; - - gtk_init (); - - main_loop = g_main_loop_new (NULL, FALSE); - - window = g_object_new (GTK_TYPE_WINDOW, - "title", "Sysprof Process List", - "default-height", 700, - "default-width", 300, - NULL); - - box = g_object_new (GTK_TYPE_BOX, - "orientation", GTK_ORIENTATION_VERTICAL, - "visible", TRUE, - NULL); - gtk_window_set_child (GTK_WINDOW (window), box); - - entry = g_object_new (GTK_TYPE_ENTRY, - "visible", TRUE, - NULL); - gtk_box_append (GTK_BOX (box), entry); - - scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW, - "visible", TRUE, - "vexpand", TRUE, - "hexpand", TRUE, - NULL); - gtk_box_append (GTK_BOX (box), scroller); - - list = g_object_new (GTK_TYPE_LIST_BOX, - "visible", TRUE, - NULL); - gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scroller), list); - - model = sysprof_process_model_new (); - sysprof_process_model_set_no_proxy (model, TRUE); - filter = sysprof_model_filter_new (G_LIST_MODEL (model)); - gtk_list_box_bind_model (GTK_LIST_BOX (list), G_LIST_MODEL (filter), create_row, NULL, NULL); - - g_signal_connect (entry, - "changed", - G_CALLBACK (on_entry_changed), - filter); - - g_signal_connect_swapped (window, "close-request", G_CALLBACK (g_main_loop_quit), main_loop); - gtk_window_present (GTK_WINDOW (window)); - g_main_loop_run (main_loop); - - g_object_unref (model); - - return 0; -} diff --git a/src/tests/test-resolvers.c b/src/tests/test-resolvers.c deleted file mode 100644 index aac7eaf9..00000000 --- a/src/tests/test-resolvers.c +++ /dev/null @@ -1,107 +0,0 @@ -#include -#include -#include - -static const SysprofCaptureSample * -read_to_next_sample (SysprofCaptureReader *reader) -{ - SysprofCaptureFrameType type; - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - if (type != SYSPROF_CAPTURE_FRAME_SAMPLE) - { - if (!sysprof_capture_reader_skip (reader)) - break; - continue; - } - - return sysprof_capture_reader_read_sample (reader); - } - - return NULL; -} - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(GPtrArray) resolvers = NULL; - g_autoptr(SysprofCaptureReader) reader = NULL; - const SysprofCaptureSample *sample; - const gchar *filename; - - if (argc != 2) - { - g_printerr ("usage: %s capture_file\n", argv[0]); - return 1; - } - - filename = argv[1]; - resolvers = g_ptr_array_new_with_free_func (g_object_unref); - g_ptr_array_add (resolvers, sysprof_elf_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_jitmap_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_kernel_symbol_resolver_new ()); - - if (!(reader = sysprof_capture_reader_new (filename))) - { - int errsv = errno; - g_error ("%s", g_strerror (errsv)); - return 1; - } - - for (guint r = 0; r < resolvers->len; r++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (resolvers, r); - - sysprof_symbol_resolver_load (resolver, reader); - sysprof_capture_reader_reset (reader); - } - - while ((sample = read_to_next_sample (reader))) - { - SysprofAddressContext last_context = SYSPROF_ADDRESS_CONTEXT_NONE; - - g_print ("Sample: pid=%d depth=%d\n", sample->frame.pid, sample->n_addrs); - - for (guint a = 0; a < sample->n_addrs; a++) - { - SysprofAddress addr = sample->addrs[a]; - SysprofAddressContext context; - gboolean found = FALSE; - - if (sysprof_address_is_context_switch (addr, &context)) - { - g_print (" %02d: %016"G_GINT64_MODIFIER"x: Context Switch\n", a, addr); - last_context = context; - continue; - } - - for (guint r = 0; r < resolvers->len; r++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (resolvers, r); - g_autofree gchar *symbol = NULL; - GQuark tag; - - symbol = sysprof_symbol_resolver_resolve_with_context (resolver, - sample->frame.time, - sample->frame.pid, - last_context, - addr, - &tag); - - if (symbol != NULL) - { - g_print (" %02d: %016"G_GINT64_MODIFIER"x: %s\n", a, addr, symbol); - found = TRUE; - break; - } - } - - if (!found) - g_print (" %02d: %016"G_GINT64_MODIFIER"x: \n", a, addr); - } - } - - return 0; -} diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c deleted file mode 100644 index 4b27b70d..00000000 --- a/src/tests/test-utils.c +++ /dev/null @@ -1,49 +0,0 @@ -/* test-utils.c - * - * Copyright 2021 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include - -static void -test_debug_dirs (void) -{ - g_auto(GStrv) test1 = NULL; - g_auto(GStrv) test2 = NULL; - - test1 = _sysprof_symbol_resolver_guess_debug_dirs ("/usr/bin/foo"); - g_assert_nonnull (test1); - g_assert_cmpstr (test1[0], ==, "/usr/lib/debug/bin"); - g_assert_cmpstr (test1[1], ==, "/usr/lib64/debug/bin"); - g_assert_cmpstr (test1[2], ==, NULL); - - test2 = _sysprof_symbol_resolver_guess_debug_dirs ("/usr/lib64/libc.so.6"); - g_assert_nonnull (test2); - g_assert_cmpstr (test2[0], ==, "/usr/lib/debug/lib64"); - g_assert_cmpstr (test2[1], ==, "/usr/lib64/debug/lib64"); - g_assert_cmpstr (test2[2], ==, NULL); -} - -int -main (int argc, - char *argv[]) -{ - g_test_init (&argc, &argv, NULL); - g_test_add_func ("/Sysprof/SymbolResolver/guess_debug_dirs", test_debug_dirs); - return g_test_run (); -} diff --git a/src/tests/test-zoom.c b/src/tests/test-zoom.c deleted file mode 100644 index 0881ab67..00000000 --- a/src/tests/test-zoom.c +++ /dev/null @@ -1,46 +0,0 @@ -#include - -static void -test_zoom_manager (void) -{ - SysprofZoomManager *zoom; - - zoom = sysprof_zoom_manager_new (); - g_assert_cmpfloat (1.0, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_in (zoom); - g_assert_cmpfloat (1.5, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_in (zoom); - g_assert_cmpfloat (2.0, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_in (zoom); - g_assert_cmpfloat (2.5, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (2.0, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (1.5, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (1.0, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (.9, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (.8, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (.67, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (.5, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (.3, ==, sysprof_zoom_manager_get_zoom (zoom)); - sysprof_zoom_manager_zoom_out (zoom); - g_assert_cmpfloat (.3 / 2, ==, sysprof_zoom_manager_get_zoom (zoom)); - - g_object_unref (zoom); -} - - -gint -main (gint argc, - gchar *argv[]) -{ - g_test_init (&argc, &argv, NULL); - g_test_add_func ("/ZoomManager/basic", test_zoom_manager); - return g_test_run (); -} diff --git a/src/tools/list-threads.c b/src/tools/list-threads.c deleted file mode 100644 index 2a60540c..00000000 --- a/src/tools/list-threads.c +++ /dev/null @@ -1,94 +0,0 @@ -/* list-threads.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include -#include -#include -#include -#include - -#include "../libsysprof/sysprof-capture-autocleanups.h" - -static bool -foreach_cb (const SysprofCaptureFrame *frame, - void *user_data) -{ - const SysprofCaptureSample *sample = (SysprofCaptureSample *)frame; - GHashTable *seen = user_data; - - if (!g_hash_table_contains (seen, GINT_TO_POINTER (sample->tid))) - g_hash_table_insert (seen, - GINT_TO_POINTER (sample->tid), - GINT_TO_POINTER (frame->pid)); - - return true; -} - -gint -main (gint argc, - gchar *argv[]) -{ - static const SysprofCaptureFrameType types[] = { - SYSPROF_CAPTURE_FRAME_SAMPLE, - }; - g_autoptr(GHashTable) seen = NULL; - g_autoptr(SysprofCaptureReader) reader = NULL; - g_autoptr(SysprofCaptureCursor) cursor = NULL; - - if (argc != 2) - { - g_printerr ("usage: %s CAPTURE_FILE\n", argv[0]); - return EXIT_FAILURE; - } - - if (g_strcmp0 ("-", argv[1]) == 0) - reader = sysprof_capture_reader_new_from_fd (dup (STDIN_FILENO)); - else - reader = sysprof_capture_reader_new (argv[1]); - - if (reader == NULL) - { - g_printerr ("Failed to open %s capture\n", argv[1]); - return EXIT_FAILURE; - } - - seen = g_hash_table_new (NULL, NULL); - - cursor = sysprof_capture_cursor_new (reader); - sysprof_capture_cursor_add_condition (cursor, - sysprof_capture_condition_new_where_type_in (G_N_ELEMENTS (types), types)); - sysprof_capture_cursor_foreach (cursor, foreach_cb, seen); - - { - GHashTableIter iter; - gpointer k, v; - - g_hash_table_iter_init (&iter, seen); - - while (g_hash_table_iter_next (&iter, &k, &v)) - { - g_print ("pid=%d tid=%d\n", - GPOINTER_TO_INT (v), - GPOINTER_TO_INT (k)); - } - } - - return EXIT_SUCCESS; -} diff --git a/src/tools/meson.build b/src/tools/meson.build deleted file mode 100644 index c48c4e10..00000000 --- a/src/tools/meson.build +++ /dev/null @@ -1,58 +0,0 @@ -tools_deps = [ - dependency('glib-2.0'), - libsysprof_capture_dep, -] - -tools_cflags = [ '-DSYSPROF_COMPILATION '] - -sysprof_cli = executable('sysprof-cli', 'sysprof-cli.c', - dependencies: [libsysprof_static_dep, polkit_dep, polkit_agent_dep], - c_args: tools_cflags, - install_dir: get_option('bindir'), - install: true, -) - -sysprof_cat = executable('sysprof-cat', 'sysprof-cat.c', - dependencies: tools_deps, - c_args: tools_cflags, - install: false, -) - -if get_option('libsysprof') - sysprof_dump = executable('sysprof-dump', 'sysprof-dump.c', - dependencies: [libsysprof_dep], - c_args: tools_cflags, - install: false, - ) - - rewrite_pid = executable('rewrite-pid', ['rewrite-pid.c'], - dependencies: [libsysprof_dep], - c_args: tools_cflags, - install: false, - ) -endif - -if get_option('sysprofd') == 'bundled' or get_option('libsysprof') - sysprof_profiler_ctl = executable('sysprof-profiler-ctl', - [ 'sysprof-profiler-ctl.c', ipc_profiler_src ], - dependencies: [ tools_deps, dependency('gio-unix-2.0', version: glib_req_version) ], - c_args: tools_cflags, - install: false, - ) -endif - -list_threads = executable('list-threads', ['list-threads.c'], - dependencies: [ tools_deps ], - c_args: tools_cflags, - install: false, -) - -if get_option('agent') - sysprof_agent = executable('sysprof-agent', - ['sysprof-agent.c', ipc_agent_src], - dependencies: [libsysprof_static_dep], - c_args: tools_cflags, - install_dir: get_option('bindir'), - install: true, - ) -endif diff --git a/src/tools/sysprof-cat.c b/src/tools/sysprof-cat.c deleted file mode 100644 index 3bbac8a9..00000000 --- a/src/tools/sysprof-cat.c +++ /dev/null @@ -1,118 +0,0 @@ -/* sysprof-cat.c - * - * Copyright 2018-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-cat" - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "../libsysprof/sysprof-capture-autocleanups.h" -#include "sysprof-capture-util-private.h" - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(SysprofCaptureWriter) writer = NULL; - g_autofree gchar *contents = NULL; - g_autofree gchar *tmpname = NULL; - gsize len; - gint fd; - - if (argc == 1) - return 0; - - if (isatty (STDOUT_FILENO)) - { - g_printerr ("stdout is a TTY, refusing to write binary data to stdout.\n"); - return EXIT_FAILURE; - } - - for (guint i = 1; i < argc; i++) - { - if (!g_file_test (argv[i], G_FILE_TEST_IS_REGULAR)) - { - g_printerr ("\"%s\" is not a regular file.\n", argv[i]); - return EXIT_FAILURE; - } - } - - if (-1 == (fd = g_file_open_tmp (".sysprof-cat-XXXXXX", &tmpname, NULL))) - { - g_printerr ("Failed to create memfd for capture file.\n"); - return EXIT_FAILURE; - } - - writer = sysprof_capture_writer_new_from_fd (fd, 4096*4); - - for (guint i = 1; i < argc; i++) - { - g_autoptr(SysprofCaptureReader) reader = NULL; - - if (!(reader = sysprof_capture_reader_new (argv[i]))) - { - int errsv = errno; - g_printerr ("Failed to create reader for \"%s\": %s\n", - argv[i], g_strerror (errsv)); - return EXIT_FAILURE; - } - - if (!sysprof_capture_writer_cat (writer, reader)) - { - int errsv = errno; - g_printerr ("Failed to join \"%s\": %s\n", - argv[i], g_strerror (errsv)); - return EXIT_FAILURE; - } - } - - if (g_file_get_contents (tmpname, &contents, &len, NULL)) - { - const gchar *buf = contents; - gsize to_write = len; - - while (to_write > 0) - { - gssize n_written; - - n_written = _sysprof_write (STDOUT_FILENO, buf, to_write); - - if (n_written < 0) - { - g_printerr ("%s\n", g_strerror (errno)); - g_unlink (tmpname); - return EXIT_FAILURE; - } - - buf += n_written; - to_write -= n_written; - } - } - - g_unlink (tmpname); - - return EXIT_SUCCESS; -} diff --git a/src/tools/sysprof-dump.c b/src/tools/sysprof-dump.c deleted file mode 100644 index f1d97064..00000000 --- a/src/tools/sysprof-dump.c +++ /dev/null @@ -1,401 +0,0 @@ -/* sysprof-dump.c - * - * Copyright 2016-2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#include "config.h" - -#include -#include -#include - -#include - -#include -#include - -static gboolean list_files = FALSE; -static const GOptionEntry main_entries[] = { - { "list-files", 'l', 0, G_OPTION_ARG_NONE, &list_files, "List files within the capture" }, - { 0 } -}; - -static char * -symbolize (GPtrArray *resolvers, - const SysprofCaptureFrame *frame, - SysprofAddressContext *context, - SysprofAddress address) -{ - SysprofAddressContext last_context = *context; - - if (sysprof_address_is_context_switch (address, context)) - return g_strdup (sysprof_address_context_to_string (last_context)); - - if (last_context == SYSPROF_ADDRESS_CONTEXT_NONE) - last_context = SYSPROF_ADDRESS_CONTEXT_USER; - - for (guint j = 0; j < resolvers->len; j++) - { - SysprofSymbolResolver *resolver = g_ptr_array_index (resolvers, j); - GQuark tag = 0; - char *str; - - str = sysprof_symbol_resolver_resolve_with_context (resolver, frame->time, frame->pid, last_context, address, &tag); - - if (str != NULL) - return str; - } - - return g_strdup ("??"); -} - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(GOptionContext) option_context = g_option_context_new ("- dump capture data"); - g_autoptr(GPtrArray) resolvers = NULL; - g_autoptr(GError) error = NULL; - SysprofCaptureReader *reader; - SysprofCaptureFrameType type; - GHashTable *ctrtypes; - gint64 begin_time; - gint64 end_time; - - g_option_context_add_main_entries (option_context, main_entries, NULL); - if (!g_option_context_parse (option_context, &argc, &argv, &error)) - { - g_printerr ("%s\n", error->message); - return EXIT_FAILURE; - } - - if (argc != 2) - { - g_printerr ("usage: %s FILENAME\n", argv[0]); - return EXIT_FAILURE; - } - - if ((reader = sysprof_capture_reader_new (argv[1])) == NULL) - { - int errsv = errno; - g_printerr ("%s\n", g_strerror (errsv)); - return EXIT_FAILURE; - } - - if (list_files) - { - g_autofree const gchar **files = sysprof_capture_reader_list_files (reader); - - for (gsize i = 0; files[i]; i++) - g_print ("%s\n", files[i]); - - return EXIT_SUCCESS; - } - - resolvers = g_ptr_array_new_with_free_func (g_object_unref); - g_ptr_array_add (resolvers, sysprof_capture_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_kernel_symbol_resolver_new ()); - g_ptr_array_add (resolvers, sysprof_elf_symbol_resolver_new ()); - - for (guint i = 0; i < resolvers->len; i++) - { - sysprof_capture_reader_reset (reader); - sysprof_symbol_resolver_load (g_ptr_array_index (resolvers, i), reader); - } - - sysprof_capture_reader_reset (reader); - - ctrtypes = g_hash_table_new (NULL, NULL); - - begin_time = sysprof_capture_reader_get_start_time (reader); - -#define SET_CTR_TYPE(i,t) g_hash_table_insert(ctrtypes, GINT_TO_POINTER(i), GINT_TO_POINTER(t)) -#define GET_CTR_TYPE(i) GPOINTER_TO_INT(g_hash_table_lookup(ctrtypes, GINT_TO_POINTER(i))) - - begin_time = sysprof_capture_reader_get_start_time (reader); - end_time = sysprof_capture_reader_get_end_time (reader); - - g_print ("Capture Time Range: %" G_GUINT64_FORMAT " to %" G_GUINT64_FORMAT " (%lf)\n", - begin_time, end_time, (end_time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC); - - while (sysprof_capture_reader_peek_type (reader, &type)) - { - switch (type) - { - case SYSPROF_CAPTURE_FRAME_EXIT: - { - const SysprofCaptureExit *ex = sysprof_capture_reader_read_exit (reader); - - if (ex == NULL) - return EXIT_FAILURE; - - g_print ("EXIT: pid=%d\n", ex->frame.pid); - - break; - } - - case SYSPROF_CAPTURE_FRAME_FORK: - { - const SysprofCaptureFork *fk = sysprof_capture_reader_read_fork (reader); - - if (fk == NULL) - return EXIT_FAILURE; - - g_print ("FORK: pid=%d child_pid=%d\n", fk->frame.pid, fk->child_pid); - - break; - } - - case SYSPROF_CAPTURE_FRAME_OVERLAY: - { - const SysprofCaptureOverlay *pr = sysprof_capture_reader_read_overlay (reader); - const char *src = pr->data; - const char *dst = &pr->data[pr->src_len+1]; - - if (pr == NULL) - return EXIT_FAILURE; - - g_print ("OVERLAY: pid=%d layer=%u src=%s dst=%s\n", pr->frame.pid, pr->layer, src, dst); - - break; - } - - case SYSPROF_CAPTURE_FRAME_JITMAP: - { - const SysprofCaptureJitmap *jitmap = sysprof_capture_reader_read_jitmap (reader); - SysprofCaptureJitmapIter iter; - SysprofCaptureAddress addr; - const gchar *str; - - if (jitmap == NULL) - return EXIT_FAILURE; - - g_print ("JITMAP:\n"); - - sysprof_capture_jitmap_iter_init (&iter, jitmap); - while (sysprof_capture_jitmap_iter_next (&iter, &addr, &str)) - g_print (" " SYSPROF_CAPTURE_ADDRESS_FORMAT " : %s\n", addr, str); - - break; - } - - case SYSPROF_CAPTURE_FRAME_LOG: - { - const SysprofCaptureLog *log = sysprof_capture_reader_read_log (reader); - gdouble ptime = (log->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - - g_print ("LOG: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n" - " severity = %d\n" - " domain = %s\n" - " message = %s\n", - log->frame.pid, log->frame.time, ptime, - log->severity, log->domain, log->message); - - break; - } - - case SYSPROF_CAPTURE_FRAME_MAP: - { - const SysprofCaptureMap *map = sysprof_capture_reader_read_map (reader); - - g_print ("MAP: pid=%d time=%" G_GINT64_FORMAT "\n" - " start = %" G_GUINT64_FORMAT "\n" - " end = %" G_GUINT64_FORMAT "\n" - " offset = %" G_GUINT64_FORMAT "\n" - " inode = %" G_GUINT64_FORMAT "\n" - " filename = %s\n", - map->frame.pid, map->frame.time, - map->start, map->end, map->offset, map->inode, map->filename); - - break; - } - - case SYSPROF_CAPTURE_FRAME_FILE_CHUNK: - { - const SysprofCaptureFileChunk *file_chunk = sysprof_capture_reader_read_file (reader); - gdouble ptime = (file_chunk->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - - g_print ("FILE_CHUNK: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n" - " path = %s\n" - " is_last = %d\n" - " bytes = %d\n", - file_chunk->frame.pid, file_chunk->frame.time, ptime, - file_chunk->path, file_chunk->is_last, file_chunk->len); - - break; - } - - case SYSPROF_CAPTURE_FRAME_MARK: - { - const SysprofCaptureMark *mark = sysprof_capture_reader_read_mark (reader); - gdouble ptime = (mark->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - - g_print ("MARK: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n" - " group = %s\n" - " name = %s\n" - " duration = %" G_GUINT64_FORMAT "\n" - " message = %s\n", - mark->frame.pid, mark->frame.time, ptime, - mark->group, mark->name, mark->duration, mark->message); - - break; - } - - case SYSPROF_CAPTURE_FRAME_METADATA: - { - const SysprofCaptureMetadata *metadata = sysprof_capture_reader_read_metadata (reader); - gdouble ptime = (metadata->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - - g_print ("METADATA: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n" - " id = %s\n" - "\"\"\"\n%s\n\"\"\"\n", - metadata->frame.pid, metadata->frame.time, ptime, - metadata->id, metadata->metadata); - - break; - } - - case SYSPROF_CAPTURE_FRAME_PROCESS: - { - const SysprofCaptureProcess *pr = sysprof_capture_reader_read_process (reader); - - if (pr == NULL) - perror ("Failed to read process"); - - g_print ("PROCESS: pid=%d cmdline=%s time=%" G_GINT64_FORMAT "\n", pr->frame.pid, pr->cmdline, pr->frame.time); - - break; - } - - case SYSPROF_CAPTURE_FRAME_SAMPLE: - { - const SysprofCaptureSample *s = sysprof_capture_reader_read_sample (reader); - gdouble ptime = (s->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; - - g_print ("SAMPLE: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n", s->frame.pid, s->frame.time, ptime); - - for (guint i = 0; i < s->n_addrs; i++) - { - g_autofree char *name = symbolize (resolvers, &s->frame, &context, s->addrs[i]); - - g_print (" " SYSPROF_CAPTURE_ADDRESS_FORMAT " (%s)\n", s->addrs[i], name); - } - - break; - } - - case SYSPROF_CAPTURE_FRAME_TIMESTAMP: - { - const SysprofCaptureTimestamp *ts = sysprof_capture_reader_read_timestamp (reader); - g_print ("TIMESTAMP: pid=%d time=%" G_GINT64_FORMAT "\n", ts->frame.pid, ts->frame.time); - break; - } - - case SYSPROF_CAPTURE_FRAME_CTRDEF: - { - const SysprofCaptureCounterDefine *def = sysprof_capture_reader_read_counter_define (reader); - gdouble ptime = (def->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - guint i; - - g_print ("NEW COUNTERS: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n", def->frame.pid, def->frame.time, ptime); - - for (i = 0; i < def->n_counters; i++) - { - const SysprofCaptureCounter *ctr = &def->counters[i]; - - SET_CTR_TYPE (ctr->id, ctr->type); - - g_print (" COUNTER(%d): %s\n" - " %s\n" - " %s\n" - "\n", - ctr->id, - ctr->category, - ctr->name, - ctr->description); - } - } - break; - - case SYSPROF_CAPTURE_FRAME_CTRSET: - { - const SysprofCaptureCounterSet *set = sysprof_capture_reader_read_counter_set (reader); - gdouble ptime = (set->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - guint i; - - g_print ("SET COUNTERS: pid=%d time=%" G_GINT64_FORMAT " (%lf)\n", set->frame.pid, set->frame.time, ptime); - - for (i = 0; i < set->n_values; i++) - { - const SysprofCaptureCounterValues *values = &set->values[i]; - guint j; - - for (j = 0; j < G_N_ELEMENTS (values->ids); j++) - { - if (values->ids[j]) - { - if (GET_CTR_TYPE (values->ids[j]) == SYSPROF_CAPTURE_COUNTER_INT64) - g_print (" COUNTER(%d): %" G_GINT64_FORMAT "\n", - values->ids[j], - values->values[j].v64); - else if (GET_CTR_TYPE (values->ids[j]) == SYSPROF_CAPTURE_COUNTER_DOUBLE) - g_print (" COUNTER(%d): %lf\n", - values->ids[j], - values->values[j].vdbl); - } - } - } - } - break; - - case SYSPROF_CAPTURE_FRAME_ALLOCATION: - { - const SysprofCaptureAllocation *ev = sysprof_capture_reader_read_allocation (reader); - gdouble ptime = (ev->frame.time - begin_time) / (gdouble)SYSPROF_NSEC_PER_SEC; - SysprofAddressContext context = SYSPROF_ADDRESS_CONTEXT_NONE; - - g_print ("%s: pid=%d tid=%d addr=0x%" G_GINT64_MODIFIER "x size=%" G_GINT64_FORMAT " time=%" G_GINT64_FORMAT " (%lf)\n", - ev->alloc_size > 0 ? "ALLOC" : "FREE", - ev->frame.pid, ev->tid, - ev->alloc_addr, ev->alloc_size, - ev->frame.time, ptime); - - for (guint i = 0; i < ev->n_addrs; i++) - { - g_autofree char *name = symbolize (resolvers, &ev->frame, &context, ev->addrs[i]); - - g_print (" " SYSPROF_CAPTURE_ADDRESS_FORMAT " (%s)\n", ev->addrs[i], name); - } - } - break; - - default: - g_print ("Skipping unknown frame type: (%d): ", type); - if (!sysprof_capture_reader_skip (reader)) - { - g_print ("Failed\n"); - return EXIT_FAILURE; - } - g_print ("Success\n"); - break; - } - } - - return EXIT_SUCCESS; -} diff --git a/src/tools/sysprof-profiler-ctl.c b/src/tools/sysprof-profiler-ctl.c deleted file mode 100644 index 323147a6..00000000 --- a/src/tools/sysprof-profiler-ctl.c +++ /dev/null @@ -1,197 +0,0 @@ -/* sysprof-profiler-ctl.c - * - * Copyright 2019 Christian Hergert - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -#define G_LOG_DOMAIN "sysprof-profiler-ctl" - -#include "config.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ipc-profiler.h" - -static gboolean opt_force; -static gboolean opt_system; -static gboolean opt_session; -static gchar *opt_address; -static gchar *opt_dest; -static gchar *opt_object_path; -static gint opt_timeout = -1; - -static const GOptionEntry main_entries[] = { - { "system", 'y', 0, G_OPTION_ARG_NONE, &opt_system, N_("Connect to the system bus") }, - { "session", 'e', 0, G_OPTION_ARG_NONE, &opt_session, N_("Connect to the session bus") }, - { "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, N_("Connect to the given D-Bus address") }, - { "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_dest, N_("Destination D-Bus name to invoke method on") }, - { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, N_("Object path to invoke method on"), N_("/org/gnome/Sysprof3/Profiler") }, - { "timeout", 't', 0, G_OPTION_ARG_INT, &opt_timeout, N_("Timeout in seconds") }, - { "force", 'f', 0, G_OPTION_ARG_NONE, &opt_force, N_("Overwrite FILENAME if it exists") }, - { 0 } -}; - -static gboolean -handle_sigint (gpointer data) -{ - GMainLoop *main_loop = data; - g_printerr ("\nSIGINT received, stopping profiler.\n"); - g_main_loop_quit (main_loop); - return G_SOURCE_REMOVE; -} - -gint -main (gint argc, - gchar *argv[]) -{ - g_autoptr(GOptionContext) context = NULL; - g_autoptr(GDBusConnection) bus = NULL; - g_autoptr(GUnixFDList) fd_list = NULL; - g_autoptr(GVariant) reply = NULL; - g_autoptr(GError) error = NULL; - g_autoptr(GMainLoop) main_loop = NULL; - g_autofree gchar *filename = NULL; - guint flags = O_RDWR; - gint fd; - gint handle; - - context = g_option_context_new (_("--dest=BUS_NAME [FILENAME] - connect to an embedded sysprof profiler")); - g_option_context_add_main_entries (context, main_entries, GETTEXT_PACKAGE); - - if (!g_option_context_parse (context, &argc, &argv, &error)) - { - g_printerr ("%s\n", error->message); - return EXIT_FAILURE; - } - - if (opt_dest == NULL) - { - g_printerr ("--dest= must contain a peer bus name on the D-Bus.\n"); - return EXIT_FAILURE; - } - - if ((opt_system && opt_session) || - (opt_address && (opt_system || opt_session))) - { - g_printerr ("Only one of --system --session or --address may be specified."); - return EXIT_FAILURE; - } - - if (!opt_address && !opt_session && !opt_system) - opt_session = TRUE; - - main_loop = g_main_loop_new (NULL, FALSE); - g_unix_signal_add (SIGINT, handle_sigint, main_loop); - - if (opt_session) - bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); - else if (opt_system) - bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); - else - bus = g_dbus_connection_new_for_address_sync (opt_address, - G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, - NULL, - NULL, - &error); - - if (bus == NULL) - { - g_printerr ("Failed to connect to D-Bus: %s\n", error->message); - return EXIT_FAILURE; - } - - filename = (argc == 1) ? g_strdup_printf ("sysprof.%d.syscap", getpid ()) - : g_strdup (argv[1]); - - if (!opt_force) - flags |= O_CREAT; - - if (-1 == (fd = g_open (filename, flags, 0644))) - { - g_printerr ("Failed to open file \"%s\". Try --force.\n", filename); - return EXIT_FAILURE; - } - - fd_list = g_unix_fd_list_new (); - handle = g_unix_fd_list_append (fd_list, fd, &error); - - close (fd); - fd = -1; - - if (handle == -1) - { - g_printerr ("Failed to build FD list for peer.\n"); - g_unlink (filename); - return EXIT_FAILURE; - } - - reply = g_dbus_connection_call_with_unix_fd_list_sync (bus, - opt_dest, - opt_object_path, - "org.gnome.Sysprof3.Profiler", - "Start", - g_variant_new ("(a{sv}h)", NULL, handle), - G_VARIANT_TYPE ("()"), - G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, - opt_timeout, - fd_list, - NULL, - NULL, - &error); - - if (error != NULL) - { - g_printerr ("Failed to start profiler: %s\n", error->message); - g_unlink (filename); - return EXIT_FAILURE; - } - - g_printerr ("Profiler capturing to file \"%s\"\n", filename); - - g_clear_pointer (&reply, g_variant_unref); - - g_main_loop_run (main_loop); - - reply = g_dbus_connection_call_sync (bus, - opt_dest, - opt_object_path, - "org.gnome.Sysprof3.Profiler", - "Stop", - g_variant_new ("()"), - NULL, - G_DBUS_CALL_FLAGS_NONE, - opt_timeout, - NULL, - &error); - - if (error != NULL) - { - g_printerr ("Failed to stop profiler: %s\n", error->message); - return EXIT_FAILURE; - } - - g_printerr ("Profiler stopped.\n"); - - return EXIT_SUCCESS; -}