All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squarespace.less.match.StatsInternPool Maven / Gradle / Ivy

The newest version!
package com.squarespace.less.match;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.squarespace.less.model.Combinator;
import com.squarespace.less.model.Dimension;
import com.squarespace.less.model.Keyword;
import com.squarespace.less.model.Node;
import com.squarespace.less.model.Property;
import com.squarespace.less.model.RGBColor;
import com.squarespace.less.model.TextElement;
import com.squarespace.less.model.Unit;

/**
 * Captures statistics of counts and sizes of interned values, allowing
 * us to estimate the overall memory savings. This is intended to only
 * be used during development, to assess the impact of the intern pool
 * on memory savings by parsing large sets of bulk stylesheets.
 */
public class StatsInternPool extends InternPool {

  private final Stats property_stats = new Stats();
  private final Stats keyword_stats = new Stats();
  private final Stats unit_stats = new Stats();
  private final Stats dimension_stats = new Stats();
  private final Stats element_stats = new Stats();
  private final Stats function_stats = new Stats();
  private final Stats color_hex_stats = new Stats();
  private final Stats color_keyword_stats = new Stats();

  private final Map properties = new HashMap<>();
  private final Map keywords = new HashMap<>();
  private final Map elements = new HashMap<>();

  /**
   * Produce a report of the number of interned values and the percentage
   * of the source that were interned.
   */
  public String report(long source_length, int top_values) {
    Stats total = new Stats();
    total.merge(property_stats);
    total.merge(keyword_stats);
    total.merge(unit_stats);
    total.merge(dimension_stats);
    total.merge(element_stats);
    total.merge(function_stats);
    total.merge(color_hex_stats);
    total.merge(color_keyword_stats);

    StringBuilder buf = new StringBuilder();
    buf.append("total source parsed: ").append(commas(source_length)).append("\n\n");
    stats(buf, "property", property_stats, source_length);
    stats(buf, "element", element_stats, source_length);
    stats(buf, "dimension", dimension_stats, source_length);
    stats(buf, "keyword", keyword_stats, source_length);
    stats(buf, "unit", unit_stats, source_length);
    stats(buf, "function", function_stats, source_length);
    stats(buf, "color hex", color_hex_stats, source_length);
    stats(buf, "color keyword", color_keyword_stats, source_length);
    stats(buf, "total", total, source_length);

    top(buf, "property", properties, top_values);
    top(buf, "keyword", keywords, top_values);
    top(buf, "element", elements, top_values);
    return buf.toString();
  }

  private void stats(StringBuilder buf, String name, Stats stats, long source_length) {
    String hit_pct = String.format("%.2f %%", percent(stats.hit_size, source_length));
    String miss_pct = String.format("%.2f %%", percent(stats.miss_size, source_length));
    String hit_rate = String.format("%.2f %%", percent(stats.hit_count, stats.hit_count + stats.miss_count));
    buf.append(name).append(":\n");
    buf.append("      hits: ").append(commas(stats.hit_count)).append('\n');
    buf.append("    misses: ").append(commas(stats.miss_count)).append('\n');
    buf.append("  hit rate: ").append(hit_rate).append('\n');
    buf.append("   hit len: ").append(commas(stats.hit_size)).append(" bytes (").append(hit_pct).append(")\n");
    buf.append("  miss len: ").append(commas(stats.miss_size)).append(" bytes (").append(miss_pct).append(")\n");
    buf.append('\n');
  }

  private void top(StringBuilder buf, String name, Map map, int num) {
    List> entries = map.entrySet().stream()
        .sorted((a, b) -> Integer.compare(b.getValue(), a.getValue())).collect(Collectors.toList());
    int limit = Math.min(num, entries.size());
    buf.append("Top ").append(name).append(" values:\n");
    for (int i = 0; i < limit; i++) {
      Map.Entry entry = entries.get(i);
      String fmt = String.format("  %10d  %s\n", entry.getValue(), entry.getKey());
      buf.append(fmt);
    }
    buf.append('\n');
  }

  private double percent(long n, long d) {
    return (n / (double)d) * 100.0;
  }

  private String commas(long n) {
    return String.format("%,d", n);
  }

  @Override
  public Property property(String raw, int start, int end) {
    int len = end - start;
    int ix = PROPERTY_DAT.get(raw, start, end);
    if (ix == -1) {
      property_stats.miss(len);
      String value = raw.substring(start, end);
      properties.put(value, properties.getOrDefault(value, 0) + 1);
      return new Property(value);
    }
    property_stats.hit(len);
    return PROPERTIES[ix];
  }

  @Override
  public Node keyword(String raw, int start, int end) {
    int len = end - start;
    int ix = KEYWORD_DAT.get(raw, start, end);
    if (ix == -1) {
      keyword_stats.miss(len);
      String value = raw.substring(start, end);
      keywords.put(value, keywords.getOrDefault(value, 0) + 1);
      return new Keyword(value);
    }
    keyword_stats.hit(len);
    return KEYWORDS[ix];
  }

  @Override
  public Unit unit(String raw, int start, int end) {
    int len = end - start;
    int ix = UNITS_DAT.get(raw, start, end);
    if (ix == -1) {
      unit_stats.miss(len);
      String rep = raw.substring(start, end);
      return Unit.get(rep);
    }
    unit_stats.hit(len);
    return UNITS[ix];
  }

  @Override
  public Dimension dimension(String raw, int start, int end) {
    int len = end - start;
    int ix = DIMENSIONS_DAT.get(raw, start, end);
    if (ix == -1) {
      dimension_stats.miss(len);
      return null;
    }
    dimension_stats.hit(len);
    return DIMENSIONS[ix];
  }

  @Override
  public TextElement element(Combinator comb, String raw, int start, int end) {
    int len = end - start;
    int ix = ELEMENT_DAT.get(raw, start, end);
    if (ix == -1) {
      element_stats.miss(len);
      String value = raw.substring(start, end);
      elements.put(value, elements.getOrDefault(value, 0) + 1);
      return new TextElement(comb, value);
    }
    element_stats.hit(len);
    if (comb == null) {
      return NULL_ELEMENTS[ix];
    }
    switch (comb) {
      case CHILD:
        return CHILD_ELEMENTS[ix];
      case NAMESPACE:
        return NAMESPACE_ELEMENTS[ix];
      case SIB_ADJ:
        return SIB_ADJ_ELEMENTS[ix];
      case SIB_GEN:
        return SIB_GEN_ELEMENTS[ix];
      case DESC:
      default:
        return DESC_ELEMENTS[ix];
    }
  }

  @Override
  public String function(String raw, int start, int end) {
    int len = end - start;
    int ix = FUNCTIONS_DAT.getIgnoreCase(raw, start, end);
    if (ix == -1) {
      function_stats.miss(len);
      return raw.substring(start, end).toLowerCase();
    }
    function_stats.hit(len);
    return FUNCTIONS[ix];
  }

  @Override
  public RGBColor color(String raw, int start, int end) {
    int len = end - start;
    if (raw.charAt(start) == '#') {
      int ix = COLORS_HEX_DAT.getIgnoreCase(raw, start, end);
      if (ix == -1) {
        color_hex_stats.miss(len);
        return RGBColor.fromHex(raw.substring(start, end));
      }
      color_hex_stats.hit(len);
      return COLORS_HEX[ix];
    }

    // Note: we only intern lowercase colors since there is some overlap between
    // capitalized keywords in font names and we want to avoid adjusting the case,
    // e.g. "font-family: Crimson Text;" would become "font-family: crimson Text;"
    int ix = COLORS_KEYWORD_DAT.get(raw, start, end);
    if (ix == -1) {
      color_keyword_stats.miss(len);
      return null;
    }
    color_keyword_stats.hit(len);
    return COLORS_KEYWORD[ix];
  }

  @Override
  public RGBColor keywordColor(String raw, int start, int end) {
    int len = end - start;
    int ix = COLORS_KEYWORD_DAT.get(raw, start, end);
    if (ix == -1) {
      color_keyword_stats.miss(len);
      return null;
    }
    color_keyword_stats.hit(len);
    return COLORS_KEYWORD[ix];
  }

  public static class Stats {

    public int hit_count = 0;
    public long hit_size = 0;
    public int miss_count = 0;
    public long miss_size = 0;

    public void hit(int len) {
      this.hit_count++;
      this.hit_size += len;
    }

    public void miss(int len) {
      this.miss_count++;
      this.miss_size += len;
    }

    public void merge(Stats s) {
      this.hit_count += s.hit_count;
      this.hit_size += s.hit_size;
      this.miss_count += s.miss_count;
      this.miss_size += s.miss_size;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy