| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef GARNET_LIB_SYSTEM_MONITOR_DOCKYARD_DOCKYARD_H_ |
| #define GARNET_LIB_SYSTEM_MONITOR_DOCKYARD_DOCKYARD_H_ |
| |
| #include <stdint.h> |
| |
| #include <iostream> |
| #include <map> |
| #include <mutex> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| class SystemMonitorDockyardHostTest; |
| |
| namespace dockyard { |
| |
| // An integer value representing a dockyard path. |
| typedef uint32_t DockyardId; |
| // Sample time stamp in nanoseconds. |
| typedef uint64_t SampleTimeNs; |
| // The data type of a sample value. |
| typedef uint64_t SampleValue; |
| // This is not intended to remain a std::map. This works fine for small numbers |
| // of samples and it has the API desired. So a std::map is being used while |
| // framing out the API. |
| typedef std::map<SampleTimeNs, SampleValue> SampleStream; |
| |
| // This is clearer than using the raw number. |
| constexpr SampleTimeNs kNanosecondsPerSecond = 1000000000; |
| |
| // Special value for missing sample stream. |
| constexpr SampleValue NO_STREAM = (SampleValue)-1ULL; |
| // Special value for missing data. |
| constexpr SampleValue NO_DATA = (SampleValue)-2ULL; |
| // The highest value for sample data. |
| constexpr SampleValue SAMPLE_MAX_VALUE = (SampleValue)-3ULL; |
| |
| // The slope value is scaled up to preserve decimal precision when using an |
| // integer value. To convert the slope integer (slope_value) to floating point: |
| // float slope_as_percentage = float(slope_value) * SLOPE_SCALE. |
| constexpr SampleValue SLOPE_LIMIT = 1000000ULL; |
| constexpr float SLOPE_SCALE = 100.0f / float(SLOPE_LIMIT); |
| |
| // The upper value used to represent zero to one values with integers. |
| constexpr SampleValue NORMALIZATION_RANGE = 1000000ULL; |
| |
| // For compatibility check with the Harvester. |
| constexpr uint32_t DOCKYARD_VERSION = 2; |
| |
| enum KoidType : SampleValue { |
| JOB = 100ULL, |
| PROCESS = 101ULL, |
| THREAD = 102ULL, |
| }; |
| |
| // A Sample. |
| struct Sample { |
| Sample(SampleTimeNs t, SampleValue v) : time(t), value(v) {} |
| |
| SampleTimeNs time; |
| // Sample values range from [0 to SAMPLE_MAX_VALUE]. |
| SampleValue value; |
| }; |
| |
| // Mapping between IDs and path strings. |
| struct PathInfo { |
| // The dockyard ID that corresponds to |path|, below. |
| DockyardId id; |
| // The dockyard path that corresponds to |id|, above. |
| std::string path; |
| }; |
| |
| // A stream set is a portion of a sample stream. This request allows for |
| // requesting multiple stream sets in a single request. There results will |
| // arrive in the form of a |StreamSetsResponse|. |
| // See: StreamSetsResponse. |
| struct StreamSetsRequest { |
| enum RenderStyle { |
| // When smoothing across samples, use a wider set of samples, including |
| // samples that are just outside of the sample set range. E.g. if the range |
| // is time 9 to 18, smooth over time 7 to 20. |
| WIDE_SMOOTHING, |
| // When sculpting across samples, pull the result toward the peaks and |
| // valleys in the data (rather than showing the average). |
| SCULPTING, |
| // For each column of the output, use the least value from the samples. |
| LOWEST_PER_COLUMN, |
| // For each column of the output, use the greatest value from the samples. |
| HIGHEST_PER_COLUMN, |
| // Add up the sample values for the slice of time and divide by the number |
| // of values found (i.e. take the average or mean). |
| AVERAGE_PER_COLUMN, |
| }; |
| |
| enum StreamSetsRequestFlags { |
| // Frame (or scale) the data set aesthetically. E.g. if the graph has little |
| // variance, zoom in to show that detail, rather then just having a flat |
| // vertical line in the graph. In some cases (like comparing graphs) this |
| // will be undesired. The values in the response will be in the range |
| // [0 to NORMALIZATION_RANGE]. |
| NORMALIZE = 1 << 0, |
| // Compute the slope of the curve. |
| SLOPE = 1 << 1, |
| }; |
| |
| StreamSetsRequest() |
| : request_id(0), |
| start_time_ns(0), |
| end_time_ns(0), |
| sample_count(0), |
| min(0), |
| max(0), |
| reserved(0), |
| render_style(AVERAGE_PER_COLUMN), |
| flags(0) {} |
| |
| // For matching against a StreamSetsResponse::request_id. Be sure to retain |
| // this request to properly interpret the |StreamSetsResponse|. |
| uint64_t request_id; |
| |
| // Request graph data for time range |start_time..end_time| that has |
| // |sample_count| values for each set. If the sample stream has more or less |
| // samples for that time range, virtual samples will be generated based on |
| // available samples. |
| SampleTimeNs start_time_ns; |
| SampleTimeNs end_time_ns; |
| uint64_t sample_count; |
| |
| SampleValue min; // Future use. |
| SampleValue max; // Future use. |
| uint64_t reserved; // Future use. |
| |
| RenderStyle render_style; |
| uint64_t flags; |
| |
| // Each stream is identified by a Dockyard ID. Multiple streams can be |
| // requested. Include a DockyardId for each stream of interest. |
| std::vector<DockyardId> dockyard_ids; |
| |
| bool HasFlag(StreamSetsRequestFlags flag) const; |
| |
| friend std::ostream& operator<<(std::ostream& os, |
| const StreamSetsRequest& request); |
| }; |
| |
| // A |StreamSetsResponse| is a replay for an individual |StreamSetsRequest|. |
| // See: StreamSetsRequest. |
| struct StreamSetsResponse { |
| // For matching against a StreamSetsRequest::request_id. |
| uint64_t request_id; |
| |
| // The low and high all-time values for all sample streams requested. All-time |
| // means that these low and high points might not appear in the |data_sets| |
| // below. "All sample streams" means that these points may not appear in the |
| // same sample streams. |
| SampleValue lowest_value; |
| SampleValue highest_value; |
| |
| // Each data set will correspond to a stream requested in the |
| // StreamSetsRequest::dockyard_ids. The value for each sample is normally in |
| // the range [0 to SAMPLE_MAX_VALUE]. If no value exists for the column, the |
| // value NO_DATA is used. |
| // For any DockyardId from StreamSetsRequest::dockyard_ids that isn't found, |
| // the resulting sample will have the value NO_STREAM. |
| std::vector<std::vector<SampleValue>> data_sets; |
| |
| friend std::ostream& operator<<(std::ostream& os, |
| const StreamSetsResponse& response); |
| }; |
| |
| // Lookup for a sample stream name string, given the sample stream ID. |
| typedef std::map<DockyardId, std::string> DockyardIdToPathMap; |
| typedef std::map<std::string, DockyardId> DockyardPathToIdMap; |
| |
| // Called when a connection is made between the Dockyard and Harvester on a |
| // Fuchsia device. |
| typedef std::function<void(const std::string& device_name)> |
| OnConnectionCallback; |
| |
| // Called when new streams are added or removed. Added values include their ID |
| // and string path. Removed values only have the ID. |
| // Intended to inform clients of PathInfoMap changes (so they may keep |
| // their equivalent map in sync). The racy nature of this update is not an issue |
| // because the rest of the API will cope with invalid stream IDs, so 'eventually |
| // consistent' is acceptable). |
| // Use SetDockyardPathsHandler() to install a StreamCallback callback. |
| typedef std::function<void(const std::vector<PathInfo>& add, |
| const std::vector<DockyardId>& remove)> |
| OnPathsCallback; |
| |
| // Called after (and in response to) a request is sent to |GetStreamSets()|. |
| // Use SetStreamSetsHandler() to install a StreamSetsCallback callback. |
| typedef std::function<void(const StreamSetsResponse& response)> |
| OnStreamSetsCallback; |
| |
| class Dockyard { |
| public: |
| Dockyard(); |
| ~Dockyard(); |
| |
| // Insert sample information for a given dockyard_id. Not intended for use by |
| // the GUI. |
| void AddSample(DockyardId dockyard_id, Sample sample); |
| |
| // Insert sample information for a given dockyard_id. Not intended for use by |
| // the GUI. |
| void AddSamples(DockyardId dockyard_id, std::vector<Sample> samples); |
| |
| // The *approximate* difference between host time and device time. This value |
| // is negotiated at connection time and not reevaluated. If either clock is |
| // altered this value may be wildly inaccurate. The intended use of this value |
| // is to hint the GUI when displaying sample times (not for doing CI analysis |
| // or similar computations). |
| // If the value is positive then the device clock is ahead of the host clock. |
| // Given a sample, subtract this value to get the host time. |
| // Given a host time, add this value to get device (sample) time. |
| // See: LatestSampleTimeNs() |
| SampleTimeNs DeviceDeltaTimeNs() const; |
| |
| // Helper functions to compute time. Read important details in the description |
| // of |DeviceDeltaTimeNs|. |
| SampleTimeNs DeviceTimeToHostTime(SampleTimeNs device_time_ns) const { |
| return device_time_ns - device_time_delta_ns_; |
| } |
| SampleTimeNs HostTimeToDeviceTime(SampleTimeNs host_time_ns) const { |
| return host_time_ns + device_time_delta_ns_; |
| }; |
| |
| void SetDeviceTimeDeltaNs(SampleTimeNs delta_ns); |
| |
| // The time stamp for the most recent batch of samples to arrive. The time is |
| // device time (not host time) in nanoseconds. |
| // See: DeviceDeltaTimeNs() |
| SampleTimeNs LatestSampleTimeNs() const; |
| |
| // Get Dockyard identifier for a given path. The ID values are stable |
| // throughout execution, so they may be cached. |
| // |
| // Returns a Dockyard ID that corresponds to |dockyard_path|. |
| DockyardId GetDockyardId(const std::string& dockyard_path); |
| bool GetDockyardPath(DockyardId dockyard_id, |
| std::string* dockyard_path) const; |
| |
| // Request graph data for time range |start_time..end_time| that has |
| // |sample_count| values for each set. If the sample stream has more or less |
| // samples for that time range, virtual samples will be generated based on |
| // available samples. |
| // |
| // The results will be supplied in a call to the |callback| previously set |
| // with SetStreamSetsHandler(). The |response| parameter on that callback will |
| // have the same context ID that is returned from this call to |
| // GetStreamSets() (i.e. that's how to match a response to a request). |
| // |
| // Returns unique context ID. |
| uint64_t GetStreamSets(StreamSetsRequest* request); |
| |
| // Called by server when a connection is made. |
| void OnConnection(); |
| |
| // Start collecting data from a named device. Tip: device names are normally |
| // four short words, such as "duck floor quick rock". |
| void StartCollectingFrom(const std::string& device); |
| void StopCollectingFrom(const std::string& device); |
| |
| OnConnectionCallback SetConnectionHandler(OnConnectionCallback callback); |
| |
| // Sets the function called when sample streams are added or removed. Pass |
| // nullptr as |callback| to stop receiving calls. |
| // |
| // Returns prior callback or nullptr. |
| OnPathsCallback SetDockyardPathsHandler(OnPathsCallback callback); |
| |
| // Sets the function called when sample stream data arrives in response to a |
| // call to GetStreamSets(). So, first set a handler with |
| // SetStreamSetsHandler(), then make as many GetStreamSets() calls as |
| // desired. Pass nullptr as |callback| to stop receiving calls. |
| // |
| // Returns prior callback or nullptr. |
| OnStreamSetsCallback SetStreamSetsHandler(OnStreamSetsCallback callback); |
| |
| // Generate responses and call handlers for sample requests. Not intended for |
| // use by the GUI. |
| void ProcessRequests(); |
| |
| private: |
| // TODO(smbug.com/38): avoid having a global mutex. Use a queue to update |
| // data. |
| mutable std::mutex mutex_; |
| std::thread server_thread_; |
| |
| // The time (clock) on the device will likely differ from the host. |
| SampleTimeNs device_time_delta_ns_; |
| SampleTimeNs latest_sample_time_ns_; |
| |
| // Communication with the GUI. |
| OnConnectionCallback on_connection_handler_; |
| OnPathsCallback on_paths_handler_; |
| OnStreamSetsCallback on_stream_sets_handler_; |
| std::vector<StreamSetsRequest*> pending_requests_; |
| |
| // Storage of sample data. |
| typedef std::map<DockyardId, SampleStream*> SampleStreamMap; |
| SampleStreamMap sample_streams_; |
| std::map<DockyardId, std::pair<SampleValue, SampleValue>> |
| sample_stream_low_high_; |
| |
| // Dockyard path <--> ID look up. |
| uint64_t next_context_id_; |
| DockyardPathToIdMap dockyard_path_to_id_; |
| DockyardIdToPathMap dockyard_id_to_path_; |
| |
| // Listen for incoming samples. |
| bool Initialize(); |
| |
| // Each of these Compute*() methods aggregate samples in different ways. |
| // There's no single 'true' way to represent aggregated data, so the choice |
| // is left to the caller. Which of these is used depends on the |
| // |StreamSetsRequestFlags| in the |StreamSetsRequest.flags| field. |
| void ComputeAveragePerColumn(DockyardId dockyard_id, |
| const SampleStream& sample_stream, |
| const StreamSetsRequest& request, |
| std::vector<SampleValue>* samples) const; |
| void ComputeHighestPerColumn(DockyardId dockyard_id, |
| const SampleStream& sample_stream, |
| const StreamSetsRequest& request, |
| std::vector<SampleValue>* samples) const; |
| void ComputeLowestPerColumn(DockyardId dockyard_id, |
| const SampleStream& sample_stream, |
| const StreamSetsRequest& request, |
| std::vector<SampleValue>* samples) const; |
| void ComputeSculpted(DockyardId dockyard_id, |
| const SampleStream& sample_stream, |
| const StreamSetsRequest& request, |
| std::vector<SampleValue>* samples) const; |
| void ComputeSmoothed(DockyardId dockyard_id, |
| const SampleStream& sample_stream, |
| const StreamSetsRequest& request, |
| std::vector<SampleValue>* samples) const; |
| |
| // Rework the response so that all values are in the range 0 to one million. |
| // This represents a 0.0 to 1.0 value, scaled up. |
| void NormalizeResponse(DockyardId dockyard_id, |
| const SampleStream& sample_stream, |
| const StreamSetsRequest& request, |
| std::vector<SampleValue>* samples) const; |
| |
| void ComputeLowestHighestForRequest(const StreamSetsRequest& request, |
| StreamSetsResponse* response) const; |
| |
| // The average of the lowest and highest value in the stream. |
| SampleValue OverallAverageForStream(DockyardId dockyard_id) const; |
| |
| // Gather the overall lowest and highest values encountered. |
| void ProcessSingleRequest(const StreamSetsRequest& request, |
| StreamSetsResponse* response) const; |
| |
| friend class ::SystemMonitorDockyardHostTest; |
| }; |
| |
| } // namespace dockyard |
| |
| #endif // GARNET_LIB_SYSTEM_MONITOR_DOCKYARD_DOCKYARD_H_ |